diff --git a/AspNet.Security.OAuth.Providers.sln b/AspNet.Security.OAuth.Providers.sln
index b6d50e601..b9ec15bed 100644
--- a/AspNet.Security.OAuth.Providers.sln
+++ b/AspNet.Security.OAuth.Providers.sln
@@ -184,10 +184,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C2CA4B38-A
docs\bitbucket.md = docs\bitbucket.md
docs\digitalocean.md = docs\digitalocean.md
docs\discord.md = docs\discord.md
+ docs\docusign.md = docs\docusign.md
docs\dropbox.md = docs\dropbox.md
docs\ebay.md = docs\ebay.md
docs\eveonline.md = docs\eveonline.md
docs\foursquare.md = docs\foursquare.md
+ docs\gitcode.md = docs\gitcode.md
docs\gitee.md = docs\gitee.md
docs\github.md = docs\github.md
docs\instagram.md = docs\instagram.md
@@ -219,8 +221,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C2CA4B38-A
docs\workweixin.md = docs\workweixin.md
docs\xumm.md = docs\xumm.md
docs\zendesk.md = docs\zendesk.md
- docs\docusign.md = docs\docusign.md
- docs\gitcode.md = docs\gitcode.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.Basecamp", "src\AspNet.Security.OAuth.Basecamp\AspNet.Security.OAuth.Basecamp.csproj", "{42306484-B2BF-4B52-B950-E0CDFA58B02A}"
@@ -316,6 +316,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Security.OAuth.VkId"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Security.OAuth.GitCode", "src\AspNet.Security.OAuth.GitCode\AspNet.Security.OAuth.GitCode.csproj", "{668833D5-DB6A-475F-B0FD-A03462B037B8}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Security.OAuth.Atlassian", "src\AspNet.Security.OAuth.Atlassian\AspNet.Security.OAuth.Atlassian.csproj", "{D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -734,6 +736,10 @@ Global
{668833D5-DB6A-475F-B0FD-A03462B037B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{668833D5-DB6A-475F-B0FD-A03462B037B8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -848,6 +854,7 @@ Global
{CD56ABE4-1CD2-4029-B556-E110A31A2CC4} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
{F3E62C24-5F82-4CF5-A994-0E10D04FB495} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
{668833D5-DB6A-475F-B0FD-A03462B037B8} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
+ {D2110C1B-6FE1-4D9A-81ED-93FB2AC85049} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C7B54DE2-6407-4802-AD9C-CE54BF414C8C}
diff --git a/README.md b/README.md
index 279718821..d0cf81be2 100644
--- a/README.md
+++ b/README.md
@@ -162,6 +162,7 @@ If a provider you're looking for does not exist, consider making a PR to add one
| Apple | [](https://www.nuget.org/packages/AspNet.Security.OAuth.Apple/ "Download AspNet.Security.OAuth.Apple from NuGet.org") | [](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Apple "Download AspNet.Security.OAuth.Apple from MyGet.org") | [Documentation](https://developer.apple.com/documentation/signinwithapplerestapi "Apple developer documentation") |
| ArcGIS | [](https://www.nuget.org/packages/AspNet.Security.OAuth.ArcGIS/ "Download AspNet.Security.OAuth.ArcGIS from NuGet.org") | [](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.ArcGIS "Download AspNet.Security.OAuth.ArcGIS from MyGet.org") | [Documentation](https://developers.arcgis.com/documentation/core-concepts/security-and-authentication/what-is-oauth-2/ "ArcGIS developer documentation") |
| Asana | [](https://www.nuget.org/packages/AspNet.Security.OAuth.Asana/ "Download AspNet.Security.OAuth.Asana from NuGet.org") | [](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Asana "Download AspNet.Security.OAuth.Asana from MyGet.org") | [Documentation](https://asana.com/developers/documentation/getting-started/auth "Asana developer documentation") |
+| Atlassian | [](https://www.nuget.org/packages/AspNet.Security.OAuth.Atlassian/ "Download AspNet.Security.OAuth.Atlassian from NuGet.org") | [](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Atlassian "Download AspNet.Security.OAuth.Atlassian from MyGet.org") | [Documentation](https://developer.atlassian.com/cloud/oauth/ "Atlassian developer documentation") |
| Autodesk | [](https://www.nuget.org/packages/AspNet.Security.OAuth.Autodesk/ "Download AspNet.Security.OAuth.Autodesk from NuGet.org") | [](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Autodesk "Download AspNet.Security.OAuth.Autodesk from MyGet.org") | [Documentation](https://forge.autodesk.com/en/docs/oauth/v2/developers_guide/overview/ "Autodesk developer documentation") |
| Baidu | [](https://www.nuget.org/packages/AspNet.Security.OAuth.Baidu/ "Download AspNet.Security.OAuth.Baidu from NuGet.org") | [](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Baidu "Download AspNet.Security.OAuth.Baidu from MyGet.org") | [Documentation](https://developer.baidu.com/ "Baidu developer documentation") |
| Basecamp | [](https://www.nuget.org/packages/AspNet.Security.OAuth.Basecamp/ "Download AspNet.Security.OAuth.Basecamp from NuGet.org") | [](https://www.myget.org/feed/aspnet-contrib/package/nuget/AspNet.Security.OAuth.Basecamp "Download AspNet.Security.OAuth.Basecamp from MyGet.org") | [Documentation](https://github.com/basecamp/api/blob/master/sections/authentication.md "Basecamp developer documentation") |
diff --git a/src/AspNet.Security.OAuth.Atlassian/AspNet.Security.OAuth.Atlassian.csproj b/src/AspNet.Security.OAuth.Atlassian/AspNet.Security.OAuth.Atlassian.csproj
new file mode 100644
index 000000000..4d7b46222
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Atlassian/AspNet.Security.OAuth.Atlassian.csproj
@@ -0,0 +1,24 @@
+
+
+
+ 9.1.0
+ $(DefaultNetCoreTargetFramework)
+
+
+
+
+ true
+
+
+
+ ASP.NET Core security middleware enabling Atlassian authentication.
+ smnsht
+ atlassian;aspnetcore;authentication;oauth;security
+
+
+
+
+
+
+
+
diff --git a/src/AspNet.Security.OAuth.Atlassian/AtlassianAuthenticationDefaults.cs b/src/AspNet.Security.OAuth.Atlassian/AtlassianAuthenticationDefaults.cs
new file mode 100644
index 000000000..0738337e3
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Atlassian/AtlassianAuthenticationDefaults.cs
@@ -0,0 +1,43 @@
+// Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+// See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+// for more information concerning the license and the contributors participating to this project.
+
+namespace AspNet.Security.OAuth.Atlassian;
+
+public class AtlassianAuthenticationDefaults
+{
+ ///
+ /// Default value for .
+ ///
+ public const string AuthenticationScheme = "Atlassian";
+
+ ///
+ /// Default value for .
+ ///
+ public static readonly string DisplayName = "Atlassian";
+
+ ///
+ /// Default value for .
+ ///
+ public static readonly string Issuer = "Atlassian";
+
+ ///
+ /// Default value for .
+ ///
+ public static readonly string CallbackPath = "/signin-atlassian";
+
+ ///
+ /// Default value for .
+ ///
+ public static readonly string AuthorizationEndpoint = "https://auth.atlassian.com/authorize";
+
+ ///
+ /// Default value for .
+ ///
+ public static readonly string TokenEndpoint = "https://auth.atlassian.com/oauth/token";
+
+ ///
+ /// Default value for .
+ ///
+ public static readonly string UserInformationEndpoint = "https://api.atlassian.com/me";
+}
diff --git a/src/AspNet.Security.OAuth.Atlassian/AtlassianAuthenticationExtensions.cs b/src/AspNet.Security.OAuth.Atlassian/AtlassianAuthenticationExtensions.cs
new file mode 100644
index 000000000..8d16e6c46
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Atlassian/AtlassianAuthenticationExtensions.cs
@@ -0,0 +1,72 @@
+// Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+// See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+// for more information concerning the license and the contributors participating to this project.
+
+using Microsoft.Extensions.DependencyInjection;
+
+namespace AspNet.Security.OAuth.Atlassian;
+
+///
+/// Extension methods to add Atlassian authentication capabilities to an HTTP application pipeline.
+///
+public static class AtlassianAuthenticationExtensions
+{
+ ///
+ /// Adds to the specified
+ /// , which enables Atlassian authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// A reference to this instance after the operation has completed.
+ public static AuthenticationBuilder AddAtlassian([NotNull] this AuthenticationBuilder builder)
+ {
+ return builder.AddAtlassian(AtlassianAuthenticationDefaults.AuthenticationScheme, options => { });
+ }
+
+ ///
+ /// Adds to the specified
+ /// , which enables Atlassian authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// The delegate used to configure the OpenID 2.0 options.
+ /// A reference to this instance after the operation has completed.
+ public static AuthenticationBuilder AddAtlassian(
+ [NotNull] this AuthenticationBuilder builder,
+ [NotNull] Action configuration)
+ {
+ return builder.AddAtlassian(AtlassianAuthenticationDefaults.AuthenticationScheme, configuration);
+ }
+
+ ///
+ /// Adds to the specified
+ /// , which enables Atlassian authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// The authentication scheme associated with this instance.
+ /// The delegate used to configure the Atlassian options.
+ /// The .
+ public static AuthenticationBuilder AddAtlassian(
+ [NotNull] this AuthenticationBuilder builder,
+ [NotNull] string scheme,
+ [NotNull] Action configuration)
+ {
+ return builder.AddAtlassian(scheme, AtlassianAuthenticationDefaults.DisplayName, configuration);
+ }
+
+ ///
+ /// Adds to the specified
+ /// , which enables Atlassian authentication capabilities.
+ ///
+ /// The authentication builder.
+ /// The authentication scheme associated with this instance.
+ /// The optional display name associated with this instance.
+ /// The delegate used to configure the Atlassian options.
+ /// The .
+ public static AuthenticationBuilder AddAtlassian(
+ [NotNull] this AuthenticationBuilder builder,
+ [NotNull] string scheme,
+ [CanBeNull] string caption,
+ [NotNull] Action configuration)
+ {
+ return builder.AddOAuth(scheme, caption, configuration);
+ }
+}
diff --git a/src/AspNet.Security.OAuth.Atlassian/AtlassianAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Atlassian/AtlassianAuthenticationHandler.cs
new file mode 100644
index 000000000..c63ba4f78
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Atlassian/AtlassianAuthenticationHandler.cs
@@ -0,0 +1,70 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+using System.Net.Http.Headers;
+using System.Security.Claims;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace AspNet.Security.OAuth.Atlassian;
+
+public partial class AtlassianAuthenticationHandler : OAuthHandler
+{
+ public AtlassianAuthenticationHandler(
+ [NotNull] IOptionsMonitor options,
+ [NotNull] ILoggerFactory logger,
+ [NotNull] UrlEncoder encoder)
+ : base(options, logger, encoder)
+ {
+ }
+
+ protected override async Task CreateTicketAsync(
+ [NotNull] ClaimsIdentity identity,
+ [NotNull] AuthenticationProperties properties,
+ [NotNull] OAuthTokenResponse tokens)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint);
+ request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
+
+ using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);
+ if (!response.IsSuccessStatusCode)
+ {
+ await Log.UserProfileErrorAsync(Logger, response, Context.RequestAborted);
+ throw new HttpRequestException("An error occurred while retrieving the user profile.");
+ }
+
+ using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted));
+
+ var principal = new ClaimsPrincipal(identity);
+ var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);
+ context.RunClaimActions();
+
+ await Events.CreatingTicket(context);
+ return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name);
+ }
+
+ private static partial class Log
+ {
+ internal static async Task UserProfileErrorAsync(ILogger logger, HttpResponseMessage response, CancellationToken cancellationToken)
+ {
+ UserProfileError(
+ logger,
+ response.StatusCode,
+ response.Headers.ToString(),
+ await response.Content.ReadAsStringAsync(cancellationToken));
+ }
+
+ [LoggerMessage(1, LogLevel.Error, "An error occurred while retrieving the user profile: the remote server returned a {Status} response with the following payload: {Headers} {Body}.")]
+ private static partial void UserProfileError(
+ ILogger logger,
+ System.Net.HttpStatusCode status,
+ string headers,
+ string body);
+ }
+}
diff --git a/src/AspNet.Security.OAuth.Atlassian/AtlassianAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Atlassian/AtlassianAuthenticationOptions.cs
new file mode 100644
index 000000000..e5212ba06
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Atlassian/AtlassianAuthenticationOptions.cs
@@ -0,0 +1,45 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+ * for more information concerning the license and the contributors participating to this project.
+ */
+
+using System.Linq;
+using System.Security.Claims;
+using static AspNet.Security.OAuth.Atlassian.AtlassianOAuthenticationConstants;
+
+namespace AspNet.Security.OAuth.Atlassian;
+
+public partial class AtlassianAuthenticationOptions : OAuthOptions
+{
+ public AtlassianAuthenticationOptions()
+ {
+ ClaimsIssuer = AtlassianAuthenticationDefaults.Issuer;
+ CallbackPath = AtlassianAuthenticationDefaults.CallbackPath;
+
+ AuthorizationEndpoint = AtlassianAuthenticationDefaults.AuthorizationEndpoint;
+ TokenEndpoint = AtlassianAuthenticationDefaults.TokenEndpoint;
+ UserInformationEndpoint = AtlassianAuthenticationDefaults.UserInformationEndpoint;
+
+ AdditionalAuthorizationParameters.Add("audience", "api.atlassian.com");
+ AdditionalAuthorizationParameters.Add("prompt", "consent");
+
+ Scope.Add("read:me");
+
+ ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "account_id");
+ ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
+ ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
+
+ ClaimActions.MapJsonKey(Claims.AccountType, "account_type");
+ ClaimActions.MapJsonKey(Claims.Picture, "picture");
+ ClaimActions.MapJsonKey(Claims.AccountStatus, "account_status");
+ ClaimActions.MapJsonKey(Claims.Nickname, "nickname");
+ ClaimActions.MapJsonKey(Claims.ZoneInfo, "zoneinfo");
+ ClaimActions.MapJsonKey(Claims.Locale, "locale");
+
+ ClaimActions.MapJsonSubKey(Claims.JobTitle, "extended_profile", "job_title");
+ ClaimActions.MapJsonSubKey(Claims.Organization, "extended_profile", "organization");
+ ClaimActions.MapJsonSubKey(Claims.Department, "extended_profile", "department");
+ ClaimActions.MapJsonSubKey(Claims.Location, "extended_profile", "location");
+ }
+}
diff --git a/src/AspNet.Security.OAuth.Atlassian/AtlassianOAuthenticationConstants.cs b/src/AspNet.Security.OAuth.Atlassian/AtlassianOAuthenticationConstants.cs
new file mode 100644
index 000000000..154c316cd
--- /dev/null
+++ b/src/AspNet.Security.OAuth.Atlassian/AtlassianOAuthenticationConstants.cs
@@ -0,0 +1,22 @@
+// Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+// See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+// for more information concerning the license and the contributors participating to this project.
+
+namespace AspNet.Security.OAuth.Atlassian;
+
+public static class AtlassianOAuthenticationConstants
+{
+ public static class Claims
+ {
+ public static readonly string AccountType = "urn:atlassian:account_type";
+ public static readonly string Picture = "urn:atlassian:picture";
+ public static readonly string AccountStatus = "urn:atlassian:account_status";
+ public static readonly string Nickname = "urn:atlassian:nickname";
+ public static readonly string ZoneInfo = "urn:atlassian:zoneinfo";
+ public static readonly string Locale = "urn:atlassian:locale";
+ public static readonly string JobTitle = "urn:atlassian:job_title";
+ public static readonly string Organization = "urn:atlassian:organization";
+ public static readonly string Department = "urn:atlassian:department";
+ public static readonly string Location = "urn:atlassian:location";
+ }
+}
diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Atlassian/AtlassianTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Atlassian/AtlassianTests.cs
new file mode 100644
index 000000000..3f62fdfb1
--- /dev/null
+++ b/test/AspNet.Security.OAuth.Providers.Tests/Atlassian/AtlassianTests.cs
@@ -0,0 +1,68 @@
+// Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+// See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
+// for more information concerning the license and the contributors participating to this project.
+
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace AspNet.Security.OAuth.Atlassian;
+
+public class AtlassianTests(ITestOutputHelper outputHelper) : OAuthTests(outputHelper)
+{
+ public override string DefaultScheme => AtlassianAuthenticationDefaults.AuthenticationScheme;
+
+ protected internal override void RegisterAuthentication(AuthenticationBuilder builder)
+ {
+ builder.AddAtlassian(options => ConfigureDefaults(builder, options));
+ }
+
+ public static IEnumerable