/// <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());
        }
Пример #2
0
        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
                      };
            }
        }
Пример #3
0
        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
                      };
            }
        }
Пример #4
0
        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}");
            }
        }
Пример #5
0
 protected virtual async Task <IActionResult> AuthorizationCodeGrant(TClient client, TokenRequest tokenRequest, bool validatePkce, CodeVerifierSecret codeVerifierSecret)
 {
     throw new NotImplementedException();
 }
Пример #6
0
        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}");
            }
        }