diff --git a/src/AspNet.Security.OAuth.Autodesk/AutodeskAuthenticationDefaults.cs b/src/AspNet.Security.OAuth.Autodesk/AutodeskAuthenticationDefaults.cs
index 5b9905ca9..7ff621b82 100644
--- a/src/AspNet.Security.OAuth.Autodesk/AutodeskAuthenticationDefaults.cs
+++ b/src/AspNet.Security.OAuth.Autodesk/AutodeskAuthenticationDefaults.cs
@@ -34,15 +34,15 @@ public static class AutodeskAuthenticationDefaults
///
/// Default value for .
///
- public static readonly string AuthorizationEndpoint = "https://developer.api.autodesk.com/authentication/v1/authorize";
+ public static readonly string AuthorizationEndpoint = "https://developer.api.autodesk.com/authentication/v2/authorize";
///
/// Default value for .
///
- public static readonly string TokenEndpoint = "https://developer.api.autodesk.com/authentication/v1/gettoken";
+ public static readonly string TokenEndpoint = "https://developer.api.autodesk.com/authentication/v2/token";
///
/// Default value for .
///
- public static readonly string UserInformationEndpoint = "https://developer.api.autodesk.com/userprofile/v1/users/@me";
+ public static readonly string UserInformationEndpoint = "https://api.userprofile.autodesk.com/userinfo";
}
diff --git a/src/AspNet.Security.OAuth.Autodesk/AutodeskAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Autodesk/AutodeskAuthenticationHandler.cs
index fb2db8a76..6eb2b2b1f 100644
--- a/src/AspNet.Security.OAuth.Autodesk/AutodeskAuthenticationHandler.cs
+++ b/src/AspNet.Security.OAuth.Autodesk/AutodeskAuthenticationHandler.cs
@@ -6,6 +6,7 @@
using System.Net.Http.Headers;
using System.Security.Claims;
+using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using Microsoft.Extensions.Logging;
@@ -50,6 +51,41 @@ protected override async Task CreateTicketAsync(
return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name);
}
+ protected override async Task ExchangeCodeAsync([NotNull] OAuthCodeExchangeContext context)
+ {
+ var tokenRequestParameters = new Dictionary
+ {
+ ["redirect_uri"] = context.RedirectUri,
+ ["code"] = context.Code,
+ ["grant_type"] = "authorization_code"
+ };
+
+ // PKCE https://tools.ietf.org/html/rfc7636#section-4.5, see BuildChallengeUrl
+ if (context.Properties.Items.TryGetValue(OAuthConstants.CodeVerifierKey, out var codeVerifier))
+ {
+ tokenRequestParameters[OAuthConstants.CodeVerifierKey] = codeVerifier!;
+ context.Properties.Items.Remove(OAuthConstants.CodeVerifierKey);
+ }
+
+ using var requestContent = new FormUrlEncodedContent(tokenRequestParameters!);
+ using var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
+ requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(
+ $"{Options.ClientId}:{Options.ClientSecret}"));
+ requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials);
+ requestMessage.Content = requestContent;
+ requestMessage.Version = Backchannel.DefaultRequestVersion;
+ using var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);
+ if (!response.IsSuccessStatusCode)
+ {
+ await Log.ExchangeCodeAsync(Logger, response, Context.RequestAborted);
+ return OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token."));
+ }
+
+ var body = await response.Content.ReadAsStringAsync(Context.RequestAborted);
+ return OAuthTokenResponse.Success(JsonDocument.Parse(body));
+ }
+
private static partial class Log
{
internal static async Task UserProfileErrorAsync(ILogger logger, HttpResponseMessage response, CancellationToken cancellationToken)
@@ -67,5 +103,21 @@ private static partial void UserProfileError(
System.Net.HttpStatusCode status,
string headers,
string body);
+
+ internal static async Task ExchangeCodeAsync(ILogger logger, HttpResponseMessage response, CancellationToken cancellationToken)
+ {
+ ExchangeCodeAsync(
+ logger,
+ response.StatusCode,
+ response.Headers.ToString(),
+ await response.Content.ReadAsStringAsync(cancellationToken));
+ }
+
+ [LoggerMessage(2, LogLevel.Error, "An error occurred while retrieving an access token: the remote server returned a {Status} response with the following payload: {Headers} {Body}.")]
+ static partial void ExchangeCodeAsync(
+ ILogger logger,
+ System.Net.HttpStatusCode status,
+ string headers,
+ string body);
}
}
diff --git a/src/AspNet.Security.OAuth.Autodesk/AutodeskAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Autodesk/AutodeskAuthenticationOptions.cs
index 0895504a9..c323d6b88 100644
--- a/src/AspNet.Security.OAuth.Autodesk/AutodeskAuthenticationOptions.cs
+++ b/src/AspNet.Security.OAuth.Autodesk/AutodeskAuthenticationOptions.cs
@@ -25,12 +25,11 @@ public AutodeskAuthenticationOptions()
Scope.Add("user-profile:read");
- ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "userId");
- ClaimActions.MapJsonKey(ClaimTypes.Name, "userName");
- ClaimActions.MapJsonKey(ClaimTypes.GivenName, "firstName");
- ClaimActions.MapJsonKey(ClaimTypes.Surname, "lastName");
- ClaimActions.MapJsonKey(ClaimTypes.Email, "emailId");
- ClaimActions.MapJsonKey(Claims.EmailVerified, "emailVerified");
- ClaimActions.MapJsonKey(Claims.TwoFactorEnabled, "2FaEnabled");
+ ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
+ ClaimActions.MapJsonKey(ClaimTypes.Name, "preferred_username");
+ ClaimActions.MapJsonKey(ClaimTypes.GivenName, "given_name");
+ ClaimActions.MapJsonKey(ClaimTypes.Surname, "family_name");
+ ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
+ ClaimActions.MapJsonKey(Claims.EmailVerified, "email_verified");
}
}
diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Autodesk/AutodeskTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Autodesk/AutodeskTests.cs
index 3df743306..3c46a75ff 100644
--- a/test/AspNet.Security.OAuth.Providers.Tests/Autodesk/AutodeskTests.cs
+++ b/test/AspNet.Security.OAuth.Providers.Tests/Autodesk/AutodeskTests.cs
@@ -27,7 +27,6 @@ protected internal override void RegisterAuthentication(AuthenticationBuilder bu
[InlineData(ClaimTypes.GivenName, "John")]
[InlineData(ClaimTypes.Surname, "Smith")]
[InlineData("urn:autodesk:emailverified", "True")]
- [InlineData("urn:autodesk:twofactorenabled", "False")]
public async Task Can_Sign_In_Using_Autodesk(string claimType, string claimValue)
{
// Arrange
diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Autodesk/bundle.json b/test/AspNet.Security.OAuth.Providers.Tests/Autodesk/bundle.json
index 7d92e1d2d..182cdf53b 100644
--- a/test/AspNet.Security.OAuth.Providers.Tests/Autodesk/bundle.json
+++ b/test/AspNet.Security.OAuth.Providers.Tests/Autodesk/bundle.json
@@ -2,7 +2,7 @@
"$schema": "https://raw.githubusercontent.com/justeat/httpclient-interception/master/src/HttpClientInterception/Bundles/http-request-bundle-schema.json",
"items": [
{
- "uri": "https://developer.api.autodesk.com/authentication/v1/gettoken",
+ "uri": "https://developer.api.autodesk.com/authentication/v2/token",
"method": "POST",
"contentFormat": "json",
"contentJson": {
@@ -13,16 +13,15 @@
}
},
{
- "uri": "https://developer.api.autodesk.com/userprofile/v1/users/@me",
+ "uri": "https://api.userprofile.autodesk.com/userinfo",
"contentFormat": "json",
"contentJson": {
- "userId": "my-id",
- "userName": "John Smith",
- "firstName": "John",
- "lastName": "Smith",
- "emailId": "john@john-smith.local",
- "emailVerified": true,
- "2FaEnabled": false
+ "sub": "my-id",
+ "preferred_username": "John Smith",
+ "given_name": "John",
+ "family_name": "Smith",
+ "email": "john@john-smith.local",
+ "email_verified": true
}
}
]