/// <summary> /// Is Valid OAuth 2.0 Code Verifier Secret. /// </summary> public static void Validate(this CodeVerifierSecret codeVerifierSecret) { if (codeVerifierSecret == null) { new ArgumentNullException(nameof(codeVerifierSecret)); } if (codeVerifierSecret.CodeVerifier.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(codeVerifierSecret.CodeVerifier), codeVerifierSecret.GetTypeName()); } codeVerifierSecret.CodeVerifier.ValidateMinLength(IdentityConstants.MessageLength.CodeVerifierMin, nameof(codeVerifierSecret.CodeVerifier), codeVerifierSecret.GetTypeName()); codeVerifierSecret.CodeVerifier.ValidateMaxLength(IdentityConstants.MessageLength.CodeVerifierMax, nameof(codeVerifierSecret.CodeVerifier), codeVerifierSecret.GetTypeName()); }
private async Task <TokenResponse> TokenRequestAsync(TClient client, string code, OidcUpSequenceData sequenceData) { var tokenRequest = new TokenRequest { GrantType = IdentityConstants.GrantTypes.AuthorizationCode, Code = code, ClientId = !client.SpClientId.IsNullOrWhiteSpace() ? client.SpClientId : client.ClientId, RedirectUri = sequenceData.RedirectUri, }; logger.ScopeTrace(() => $"Up, Token request '{tokenRequest.ToJsonIndented()}'.", traceType: TraceTypes.Message); var requestDictionary = tokenRequest.ToDictionary(); if (!client.ClientSecret.IsNullOrEmpty()) { var clientCredentials = new ClientCredentials { ClientSecret = client.ClientSecret, }; logger.ScopeTrace(() => $"Up, Client credentials '{new ClientCredentials { ClientSecret = $"{(clientCredentials.ClientSecret?.Length > 10 ? clientCredentials.ClientSecret.Substring(0, 3) : string.Empty)}..." }.ToJsonIndented()}'.", traceType: TraceTypes.Message); requestDictionary = requestDictionary.AddToDictionary(clientCredentials); } if (client.EnablePkce) { var codeVerifierSecret = new CodeVerifierSecret { CodeVerifier = sequenceData.CodeVerifier, }; logger.ScopeTrace(() => $"Up, Code verifier secret '{new CodeVerifierSecret { CodeVerifier = $"{(codeVerifierSecret.CodeVerifier?.Length > 10 ? codeVerifierSecret.CodeVerifier.Substring(0, 3) : string.Empty)}..." }.ToJsonIndented()}'.", traceType: TraceTypes.Message); requestDictionary = requestDictionary.AddToDictionary(codeVerifierSecret); } logger.ScopeTrace(() => $"Up, OIDC Token request URL '{client.TokenUrl}'.", traceType: TraceTypes.Message); var request = new HttpRequestMessage(HttpMethod.Post, client.TokenUrl); request.Content = new FormUrlEncodedContent(requestDictionary); using var response = await httpClientFactory.CreateClient(nameof (HttpClient)).SendAsync(request); switch (response.StatusCode) { case HttpStatusCode.OK: var result = await response.Content.ReadAsStringAsync(); var tokenResponse = result.ToObject <TokenResponse>(); logger.ScopeTrace(() => $"Up, Token response '{tokenResponse.ToJsonIndented()}'.", traceType: TraceTypes.Message); tokenResponse.Validate(true); if (tokenResponse.AccessToken.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(tokenResponse.AccessToken), tokenResponse.GetTypeName()); } if (tokenResponse.ExpiresIn <= 0) { throw new ArgumentNullException(nameof(tokenResponse.ExpiresIn), tokenResponse.GetTypeName()); } return(tokenResponse); case HttpStatusCode.BadRequest: var resultBadRequest = await response.Content.ReadAsStringAsync(); var tokenResponseBadRequest = resultBadRequest.ToObject <TokenResponse>(); logger.ScopeTrace(() => $"Up, Bad token response '{tokenResponseBadRequest.ToJsonIndented()}'.", traceType: TraceTypes.Message); try { tokenResponseBadRequest.Validate(true); } catch (ResponseErrorException rex) { throw new OAuthRequestException($"External {rex.Message}") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidRequest }; } throw new EndpointException($"Bad request. Status code '{response.StatusCode}'. Response '{resultBadRequest}'.") { RouteBinding = RouteBinding }; default: try { var resultUnexpectedStatus = await response.Content.ReadAsStringAsync(); var tokenResponseUnexpectedStatus = resultUnexpectedStatus.ToObject <TokenResponse>(); logger.ScopeTrace(() => $"Up, Unexpected status code response '{tokenResponseUnexpectedStatus.ToJsonIndented()}'.", traceType: TraceTypes.Message); try { tokenResponseUnexpectedStatus.Validate(true); } catch (ResponseErrorException rex) { throw new OAuthRequestException($"External {rex.Message}") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidRequest }; } } catch (OAuthRequestException) { throw; } catch (Exception ex) { throw new EndpointException($"Unexpected status code. Status code={response.StatusCode}", ex) { RouteBinding = RouteBinding }; } throw new EndpointException($"Unexpected status code. Status code={response.StatusCode}") { RouteBinding = RouteBinding }; } }
protected override async Task <IActionResult> AuthorizationCodeGrantAsync(TClient client, TokenRequest tokenRequest, bool validatePkce, CodeVerifierSecret codeVerifierSecret) { var authCodeGrant = await oauthAuthCodeGrantDownLogic.GetAndValidateAuthCodeGrantAsync(tokenRequest.Code, tokenRequest.RedirectUri, tokenRequest.ClientId); Console.WriteLine($"authCodeGrant not null: {authCodeGrant != null}"); if (validatePkce) { await ValidatePkceAsync(client, authCodeGrant.CodeChallenge, authCodeGrant.CodeChallengeMethod, codeVerifierSecret); } logger.ScopeTrace(() => "Down, OIDC Authorization code grant accepted.", triggerEvent: true); try { var tokenResponse = new TokenResponse { TokenType = IdentityConstants.TokenTypes.Bearer, ExpiresIn = client.AccessTokenLifetime, }; string algorithm = IdentityConstants.Algorithms.Asymmetric.RS256; var claims = authCodeGrant.Claims.ToClaimList(); var scopes = authCodeGrant.Scope.ToSpaceList(); tokenResponse.AccessToken = await jwtDownLogic.CreateAccessTokenAsync(client, claims, scopes, algorithm); var responseTypes = new[] { IdentityConstants.ResponseTypes.IdToken, IdentityConstants.ResponseTypes.Token }; tokenResponse.IdToken = await jwtDownLogic.CreateIdTokenAsync(client, claims, scopes, authCodeGrant.Nonce, responseTypes, null, tokenResponse.AccessToken, algorithm); if (scopes.Contains(IdentityConstants.DefaultOidcScopes.OfflineAccess)) { tokenResponse.RefreshToken = await oauthRefreshTokenGrantDownLogic.CreateRefreshTokenGrantAsync(client, claims, authCodeGrant.Scope); } logger.ScopeTrace(() => $"Token response '{tokenResponse.ToJsonIndented()}'.", traceType: TraceTypes.Message); logger.ScopeTrace(() => "Down, OIDC Token response.", triggerEvent: true); return(new JsonResult(tokenResponse)); } catch (KeyException kex) { throw new OAuthRequestException(kex.Message, kex) { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.ServerError }; } }
private async Task <(int, ClaimsPrincipal, string, string)> AcquireTokensAsync(OpenidConnectPkceState openidClientPkceState, string code) { var tokenRequest = new TokenRequest { GrantType = IdentityConstants.GrantTypes.AuthorizationCode, Code = code, ClientId = openidClientPkceState.ClientId, RedirectUri = openidClientPkceState.CallBackUri, }; var codeVerifierSecret = new CodeVerifierSecret { CodeVerifier = openidClientPkceState.CodeVerifier, }; var oidcDiscovery = await GetOidcDiscoveryAsync(openidClientPkceState.OidcDiscoveryUri); var request = new HttpRequestMessage(HttpMethod.Post, oidcDiscovery.TokenEndpoint); request.Content = new FormUrlEncodedContent(tokenRequest.ToDictionary().AddToDictionary(codeVerifierSecret)); var httpClient = serviceProvider.GetService <HttpClient>(); var response = await httpClient.SendAsync(request); switch (response.StatusCode) { case HttpStatusCode.OK: var result = await response.Content.ReadAsStringAsync(); var tokenResponse = result.ToObject <TokenResponse>(); tokenResponse.Validate(true); if (tokenResponse.AccessToken.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(tokenResponse.AccessToken), tokenResponse.GetTypeName()); } if (tokenResponse.ExpiresIn <= 0) { throw new ArgumentNullException(nameof(tokenResponse.ExpiresIn), tokenResponse.GetTypeName()); } var oidcDiscoveryKeySet = await GetOidcDiscoveryKeysAsync(openidClientPkceState.OidcDiscoveryUri); (var idTokenPrincipal, _) = JwtHandler.ValidateToken(tokenResponse.IdToken, oidcDiscovery.Issuer, oidcDiscoveryKeySet.Keys, openidClientPkceState.ClientId, nameClaimType: globalOpenidClientPkceSettings.NameClaimType, roleClaimType: globalOpenidClientPkceSettings.RoleClaimType); var nonce = idTokenPrincipal.Claims.Where(c => c.Type == JwtClaimTypes.Nonce).Select(c => c.Value).SingleOrDefault(); if (!openidClientPkceState.Nonce.Equals(nonce, StringComparison.Ordinal)) { throw new SecurityException("Nonce do not match."); } return(tokenResponse.ExpiresIn, idTokenPrincipal, tokenResponse.IdToken, tokenResponse.AccessToken); case HttpStatusCode.BadRequest: var resultBadRequest = await response.Content.ReadAsStringAsync(); var tokenResponseBadRequest = resultBadRequest.ToObject <TokenResponse>(); tokenResponseBadRequest.Validate(true); throw new Exception($"Error login call back, Bad request. StatusCode={response.StatusCode}"); default: throw new Exception($"Error login call back, Status Code not expected. StatusCode={response.StatusCode}"); } }
protected virtual async Task <IActionResult> AuthorizationCodeGrant(TClient client, TokenRequest tokenRequest, bool validatePkce, CodeVerifierSecret codeVerifierSecret) { throw new NotImplementedException(); }
protected async Task ValidatePkce(TClient client, string codeChallenge, string codeChallengeMethod, CodeVerifierSecret codeVerifierSecret) { codeVerifierSecret.Validate(); if (codeChallengeMethod.IsNullOrEmpty() || codeChallengeMethod.Equals(IdentityConstants.CodeChallengeMethods.Plain, StringComparison.Ordinal)) { if (!codeVerifierSecret.CodeVerifier.Equals(codeChallenge, StringComparison.Ordinal)) { throw new OAuthRequestException($"Invalid '{IdentityConstants.CodeChallengeMethods.Plain}' code verifier for client id '{client.ClientId}'.") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidGrant }; } } else if (codeChallengeMethod.Equals(IdentityConstants.CodeChallengeMethods.S256, StringComparison.Ordinal)) { var codeChallengeFromCodeVerifier = await codeVerifierSecret.CodeVerifier.Sha256HashBase64urlEncoded(); if (!codeChallengeFromCodeVerifier.Equals(codeChallenge, StringComparison.Ordinal)) { throw new OAuthRequestException($"Invalid '{IdentityConstants.CodeChallengeMethods.S256}' code verifier for client id '{client.ClientId}'.") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidGrant }; } } else { throw new OAuthRequestException($"Invalid code callenge method for client id '{client.ClientId}'.") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidGrant }; } }
private async Task <(ClaimsPrincipal idTokenPrincipal, TokenResponse tokenResponse)> AcquireTokensAsync(OpenidConnectPkceState openidClientPkceState, string code) { var tokenRequest = new TokenRequest { GrantType = IdentityConstants.GrantTypes.AuthorizationCode, Code = code, ClientId = openidClientPkceState.ClientId, RedirectUri = openidClientPkceState.CallBackUri, }; var codeVerifierSecret = new CodeVerifierSecret { CodeVerifier = openidClientPkceState.CodeVerifier, }; var oidcDiscovery = await GetOidcDiscoveryAsync(openidClientPkceState.OidcDiscoveryUri); var requestDictionary = tokenRequest.ToDictionary().AddToDictionary(codeVerifierSecret); if (openidClientPkceState.Resources?.Count() > 0) { var resourceRequest = new ResourceRequest { Resources = openidClientPkceState.Resources }; requestDictionary = requestDictionary.AddToDictionary(resourceRequest); } var request = new HttpRequestMessage(HttpMethod.Post, oidcDiscovery.TokenEndpoint); request.Content = new FormUrlEncodedContent(requestDictionary); var response = await GetHttpClient().SendAsync(request); switch (response.StatusCode) { case HttpStatusCode.OK: var result = await response.Content.ReadAsStringAsync(); var tokenResponse = result.ToObject <TokenResponse>(); tokenResponse.Validate(true); if (tokenResponse.AccessToken.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(tokenResponse.AccessToken), tokenResponse.GetTypeName()); } if (tokenResponse.ExpiresIn <= 0) { throw new ArgumentNullException(nameof(tokenResponse.ExpiresIn), tokenResponse.GetTypeName()); } // .NET 5.0 error, System.Security.Cryptography.RSA.Create() - System.PlatformNotSupportedException: System.Security.Cryptography.Algorithms is not supported on this platform. // https://github.com/dotnet/aspnetcore/issues/26123 // https://github.com/dotnet/runtime/issues/40074 // .NET 7 // https://github.com/dotnet/designs/blob/main/accepted/2021/blazor-wasm-crypto.md#net-7-plan #if !NET50 && !NET60 var oidcDiscoveryKeySet = await GetOidcDiscoveryKeysAsync(openidClientPkceState.OidcDiscoveryUri); (var idTokenPrincipal, _) = JwtHandler.ValidateToken(tokenResponse.IdToken, oidcDiscovery.Issuer, oidcDiscoveryKeySet.Keys.ToMSJsonWebKeys(), openidClientPkceState.ClientId, nameClaimType: globalOpenidClientPkceSettings.NameClaimType, roleClaimType: globalOpenidClientPkceSettings.RoleClaimType); #else var idTokenPrincipal = JwtHandler.ReadTokenClaims(tokenResponse.IdToken); #endif var nonce = idTokenPrincipal.Claims.Where(c => c.Type == JwtClaimTypes.Nonce).Select(c => c.Value).FirstOrDefault(); if (!openidClientPkceState.Nonce.Equals(nonce, StringComparison.Ordinal)) { throw new SecurityException("Nonce do not match."); } return(idTokenPrincipal, tokenResponse); case HttpStatusCode.BadRequest: var resultBadRequest = await response.Content.ReadAsStringAsync(); var tokenResponseBadRequest = resultBadRequest.ToObject <TokenResponse>(); tokenResponseBadRequest.Validate(true); throw new Exception($"Error login call back, Bad request. StatusCode={response.StatusCode}"); default: throw new Exception($"Error login call back, Status Code not expected. StatusCode={response.StatusCode}"); } }