public async Task <ResponseValidationResult> ProcessResponseAsync(
            AuthorizeResponse authorizeResponse,
            AuthorizeState state,
            Parameters backChannelParameters,
            CancellationToken cancellationToken = default)
        {
            _logger.LogTrace("ProcessResponseAsync");

            //////////////////////////////////////////////////////
            // validate common front-channel parameters
            //////////////////////////////////////////////////////

            if (string.IsNullOrEmpty(authorizeResponse.Code))
            {
                return(new ResponseValidationResult("Missing authorization code."));
            }

            if (string.IsNullOrEmpty(authorizeResponse.State))
            {
                return(new ResponseValidationResult("Missing state."));
            }

            if (!string.Equals(state.State, authorizeResponse.State, StringComparison.Ordinal))
            {
                return(new ResponseValidationResult("Invalid state."));
            }

            return(await ProcessCodeFlowResponseAsync(authorizeResponse, state, backChannelParameters, cancellationToken));
        }
Exemple #2
0
        private async Task <ResponseValidationResult> ProcessCodeFlowResponseAsync(
            AuthorizeResponse authorizeResponse,
            AuthorizeState state,
            Parameters backChannelParameters,
            CancellationToken cancellationToken)
        {
            _logger.LogTrace("ProcessCodeFlowResponseAsync");

            //////////////////////////////////////////////////////
            // process back-channel response
            //////////////////////////////////////////////////////

            // redeem code for tokens
            var tokenResponse = await RedeemCodeAsync(authorizeResponse.Code, state, backChannelParameters, cancellationToken);

            if (tokenResponse.IsError)
            {
                return(new ResponseValidationResult($"Error redeeming code: {tokenResponse.Error ?? "no error code"} / {tokenResponse.ErrorDescription ?? "no description"}"));
            }

            // validate token response
            var tokenResponseValidationResult = await ValidateTokenResponseAsync(tokenResponse, state, requireIdentityToken : false, cancellationToken : cancellationToken);

            if (tokenResponseValidationResult.IsError)
            {
                return(new ResponseValidationResult($"Error validating token response: {tokenResponseValidationResult.Error}"));
            }

            return(new ResponseValidationResult
            {
                AuthorizeResponse = authorizeResponse,
                TokenResponse = tokenResponse,
                User = tokenResponseValidationResult?.IdentityTokenValidationResult?.User ?? Principal.Create(_options.Authority)
            });
        }
        public async Task <LoginResult> ValidateResponseAsync(string data, AuthorizeState state)
        {
            var result = new LoginResult {
                Success = false
            };

            var response = new AuthorizeResponse(data);

            if (response.IsError)
            {
                result.Error = response.Error;
                return(result);
            }

            if (string.IsNullOrEmpty(response.Code))
            {
                result.Error = "missing authorization code";
                return(result);
            }

            if (_options.Style == OidcClientOptions.AuthenticationStyle.AuthorizationCode)
            {
                return(await ValidateCodeFlowResponse(response, state));
            }
            else if (_options.Style == OidcClientOptions.AuthenticationStyle.Hybrid)
            {
                return(await ValidateHybridFlowResponse(response, state));
            }

            throw new InvalidOperationException("Invalid authentication style");
        }
        private async Task <string> CreateUrlAsync(AuthorizeState state, string codeChallenge, object extraParameters)
        {
            var request = new AuthorizeRequest((await _options.GetProviderInformationAsync()).AuthorizeEndpoint);

            string responseType = null;

            if (_options.Style == OidcClientOptions.AuthenticationStyle.AuthorizationCode)
            {
                responseType = OidcConstants.ResponseTypes.Code;
            }
            else if (_options.Style == OidcClientOptions.AuthenticationStyle.Hybrid)
            {
                responseType = OidcConstants.ResponseTypes.CodeIdToken;
            }
            else
            {
                throw new InvalidOperationException("Unsupported authentication style");
            }

            var url = request.CreateAuthorizeUrl(
                clientId: _options.ClientId,
                responseType: responseType,
                scope: _options.Scope,
                redirectUri: state.RedirectUri,
                responseMode: _options.UseFormPost ? OidcConstants.ResponseModes.FormPost : null,
                nonce: state.Nonce,
                state: state.State,
                codeChallenge: codeChallenge,
                codeChallengeMethod: OidcConstants.CodeChallengeMethods.Sha256,
                extra: extraParameters);

            return(url);
        }
Exemple #5
0
        private async Task <TokenResponse> RedeemCodeAsync(string code, AuthorizeState state)
        {
            var endpoint = (await _options.GetProviderInformationAsync()).TokenEndpoint;

            var tokenClient = new TokenClient(endpoint, _options.ClientId, _options.ClientSecret);
            var tokenResult = await tokenClient.RequestAuthorizationCodeAsync(
                code,
                state.RedirectUri,
                codeVerifier : state.CodeVerifier);

            return(tokenResult);
        }
        private async Task <AuthorizeState> CreateAuthorizeStateAsync(object extraParameters = null)
        {
            var state = new AuthorizeState();

            state.Nonce       = RNG.CreateUniqueId();
            state.RedirectUri = _options.RedirectUri;

            string codeChallenge = CreateCodeChallenge(state);

            state.StartUrl = await CreateUrlAsync(state, codeChallenge, extraParameters);

            return(state);
        }
        private async Task <TokenResponse> RedeemCodeAsync(string code, AuthorizeState state)
        {
            _logger.LogTrace("RedeemCodeAsync");

            var client = GetTokenClient();

            var tokenResult = await client.RequestAuthorizationCodeAsync(
                code,
                state.RedirectUri,
                codeVerifier : state.CodeVerifier);

            return(tokenResult);
        }
        private string CreateCodeChallenge(AuthorizeState state)
        {
            state.CodeVerifier = RNG.CreateUniqueId();
            var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256);

            var challengeBuffer = sha256.HashData(
                CryptographicBuffer.CreateFromByteArray(
                    Encoding.UTF8.GetBytes(state.CodeVerifier)));

            byte[] challengeBytes;

            CryptographicBuffer.CopyToByteArray(challengeBuffer, out challengeBytes);
            return(Base64Url.Encode(challengeBytes));
        }
        private async Task <string> CreateUrlAsync(AuthorizeState state, string codeChallenge, object extraParameters)
        {
            var request = new AuthorizeRequest((await _options.GetProviderInformationAsync()).AuthorizeEndpoint);
            var url     = request.CreateAuthorizeUrl(
                clientId: _options.ClientId,
                responseType: OidcConstants.ResponseTypes.CodeIdToken,
                scope: _options.Scope,
                redirectUri: state.RedirectUri,
                responseMode: _options.UseFormPost ? OidcConstants.ResponseModes.FormPost : null,
                nonce: state.Nonce,
                codeChallenge: codeChallenge,
                codeChallengeMethod: OidcConstants.CodeChallengeMethods.Sha256,
                extra: extraParameters);

            return(url);
        }
        public AuthorizeState CreateAuthorizeState(Parameters frontChannelParameters)
        {
            _logger.LogTrace("CreateAuthorizeStateAsync");

            var pkce = _crypto.CreatePkceData();

            var state = new AuthorizeState
            {
                State        = _crypto.CreateState(_options.StateLength),
                RedirectUri  = _options.RedirectUri,
                CodeVerifier = pkce.CodeVerifier,
            };

            state.StartUrl = CreateAuthorizeUrl(state.State, pkce.CodeChallenge, frontChannelParameters);

            _logger.LogDebug(LogSerializer.Serialize(state));

            return(state);
        }
Exemple #11
0
        public AuthorizeState CreateAuthorizeState(IDictionary <string, string> extraParameters = default)
        {
            _logger.LogTrace("CreateAuthorizeStateAsync");

            var pkce = _crypto.CreatePkceData();

            var state = new AuthorizeState
            {
                Nonce        = _crypto.CreateNonce(),
                State        = _crypto.CreateState(),
                RedirectUri  = _options.RedirectUri,
                CodeVerifier = pkce.CodeVerifier,
            };

            state.StartUrl = CreateAuthorizeUrl(state.State, state.Nonce, pkce.CodeChallenge, extraParameters);

            _logger.LogDebug(LogSerializer.Serialize(state));

            return(state);
        }
        public AuthorizeState CreateAuthorizeState(object extraParameters = null)
        {
            _logger.LogTrace("CreateAuthorizeStateAsync");

            var pkce = _options.Flow != OidcClientOptions.AuthenticationFlow.Implicit ? _crypto.CreatePkceData() : null;

            var state = new AuthorizeState
            {
                Nonce        = _crypto.CreateNonce(),
                State        = _crypto.CreateState(),
                RedirectUri  = _options.RedirectUri,
                CodeVerifier = pkce?.CodeVerifier,
            };

            state.StartUrl = CreateUrl(state.State, state.Nonce, pkce?.CodeChallenge, extraParameters);

            _logger.LogDebug(LogSerializer.Serialize(state));

            return(state);
        }
Exemple #13
0
        private async Task <TokenResponse> RedeemCodeAsync(string code, AuthorizeState state, IDictionary <string, string> extraParameters, CancellationToken cancellationToken)
        {
            _logger.LogTrace("RedeemCodeAsync");

            var client      = _options.CreateClient();
            var tokenResult = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest
            {
                Address = _options.ProviderInformation.TokenEndpoint,

                ClientId              = _options.ClientId,
                ClientSecret          = _options.ClientSecret,
                ClientCredentialStyle = _options.TokenClientCredentialStyle,

                Code         = code,
                RedirectUri  = state.RedirectUri,
                CodeVerifier = state.CodeVerifier,
                Parameters   = extraParameters ?? new Dictionary <string, string>()
            }, cancellationToken).ConfigureAwait(false);

            return(tokenResult);
        }
        public AuthorizeState CreateAuthorizeState(Task <Microsoft.IdentityModel.Tokens.RsaSecurityKey> pregeneratedPoPKeyTask = null, object extraParameters = null)
        {
            _logger.LogTrace("CreateAuthorizeStateAsync");

            var pkce = _crypto.CreatePkceData();

            var state = new AuthorizeState
            {
                Nonce                  = _crypto.CreateNonce(),
                State                  = _crypto.CreateState(),
                RedirectUri            = _options.RedirectUri,
                CodeVerifier           = pkce.CodeVerifier,
                PopTokenGenerationTask = _options.RequestPopTokens ? pregeneratedPoPKeyTask ?? PopTokenExtensions.CreateProviderForPopTokenAsync() : null
            };

            state.StartUrl = CreateUrl(state.State, state.Nonce, pkce.CodeChallenge, extraParameters);

            _logger.LogDebug(LogSerializer.Serialize(state));

            return(state);
        }
        internal async Task <TokenResponseValidationResult> ValidateTokenResponseAsync(TokenResponse response, AuthorizeState state, bool requireIdentityToken, CancellationToken cancellationToken = default)
        {
            _logger.LogTrace("ValidateTokenResponse");

            // token response must contain an access token
            if (response.AccessToken.IsMissing())
            {
                return(new TokenResponseValidationResult("Access token is missing on token response."));
            }

            if (requireIdentityToken)
            {
                // token response must contain an identity token (openid scope is mandatory)
                if (response.IdentityToken.IsMissing())
                {
                    return(new TokenResponseValidationResult("Identity token is missing on token response."));
                }
            }

            if (response.IdentityToken.IsPresent())
            {
                IIdentityTokenValidator validator;
                if (_options.IdentityTokenValidator == null)
                {
                    if (_options.Policy.RequireIdentityTokenSignature == false)
                    {
                        validator = new NoValidationIdentityTokenValidator();
                    }
                    else
                    {
                        throw new InvalidOperationException("No IIdentityTokenValidator is configured. Either explicitly set a validator on the options, or set OidcClientOptions.Policy.RequireIdentityTokenSignature to false to skip validation.");
                    }
                }
                else
                {
                    validator = _options.IdentityTokenValidator;
                }

                var validationResult = await validator.ValidateAsync(response.IdentityToken, _options, cancellationToken);

                if (validationResult.Error == "invalid_signature")
                {
                    await _refreshKeysAsync(cancellationToken);

                    validationResult = await _options.IdentityTokenValidator.ValidateAsync(response.IdentityToken, _options, cancellationToken);
                }

                if (validationResult.IsError)
                {
                    return(new TokenResponseValidationResult(validationResult.Error ?? "Identity token validation error"));
                }

                // validate at_hash
                if (!string.Equals(validationResult.SignatureAlgorithm, "none", StringComparison.OrdinalIgnoreCase))
                {
                    var atHash = validationResult.User.FindFirst(JwtClaimTypes.AccessTokenHash);
                    if (atHash == null)
                    {
                        if (_options.Policy.RequireAccessTokenHash)
                        {
                            return(new TokenResponseValidationResult("at_hash is missing."));
                        }
                    }
                    else
                    {
                        if (!_crypto.ValidateHash(response.AccessToken, atHash.Value, validationResult.SignatureAlgorithm))
                        {
                            return(new TokenResponseValidationResult("Invalid access token hash."));
                        }
                    }
                }

                return(new TokenResponseValidationResult(validationResult));
            }

            return(new TokenResponseValidationResult((IdentityTokenValidationResult)null));
        }
Exemple #16
0
        /// <summary>
        /// Processes the authorize response.
        /// </summary>
        /// <param name="data">The response data.</param>
        /// <param name="state">The state.</param>
        /// <returns>Result of the login response validation</returns>
        public async Task <LoginResult> ProcessResponseAsync(string data, AuthorizeState state)
        {
            _logger.LogTrace("ProcessResponseAsync");
            _logger.LogInformation("Processing response.");

            await EnsureConfigurationAsync();

            _logger.LogDebug("Authorize response: {response}", data);
            var authorizeResponse = new AuthorizeResponse(data);

            if (authorizeResponse.IsError)
            {
                _logger.LogError(authorizeResponse.Error);
                return(new LoginResult(authorizeResponse.Error));
            }

            var result = await _processor.ProcessResponseAsync(authorizeResponse, state);

            if (result.IsError)
            {
                _logger.LogError(result.Error);
                return(new LoginResult(result.Error));
            }

            var userInfoClaims = Enumerable.Empty <Claim>();

            if (_options.LoadProfile)
            {
                var userInfoResult = await GetUserInfoAsync(result.TokenResponse.AccessToken);

                if (userInfoResult.IsError)
                {
                    var error = $"Error contacting userinfo endpoint: {userInfoResult.Error}";
                    _logger.LogError(error);

                    return(new LoginResult(error));
                }

                userInfoClaims = userInfoResult.Claims;

                var userInfoSub = userInfoClaims.FirstOrDefault(c => c.Type == JwtClaimTypes.Subject);
                if (userInfoSub == null)
                {
                    var error = "sub claim is missing from userinfo endpoint";
                    _logger.LogError(error);

                    return(new LoginResult(error));
                }

                if (!string.Equals(userInfoSub.Value, result.User.FindFirst(JwtClaimTypes.Subject).Value))
                {
                    var error = "sub claim from userinfo endpoint is different than sub claim from identity token.";
                    _logger.LogError(error);

                    return(new LoginResult(error));
                }
            }

            var user = ProcessClaims(result.User, userInfoClaims);

            var loginResult = new LoginResult
            {
                User                  = user,
                AccessToken           = result.TokenResponse.AccessToken,
                RefreshToken          = result.TokenResponse.RefreshToken,
                AccessTokenExpiration = DateTime.Now.AddSeconds(result.TokenResponse.ExpiresIn),
                IdentityToken         = result.TokenResponse.IdentityToken,
                AuthenticationTime    = DateTime.Now
            };

            if (!string.IsNullOrWhiteSpace(loginResult.RefreshToken))
            {
                loginResult.RefreshTokenHandler = new RefreshTokenHandler(
                    TokenClientFactory.Create(_options),
                    loginResult.RefreshToken,
                    loginResult.AccessToken);
            }

            return(loginResult);
        }
        private async Task <Tuple <TokenResponse, RsaSecurityKey> > RedeemCodeAsync(string code, AuthorizeState state)
        {
            _logger.LogTrace("RedeemCodeAsync");

            var client = GetTokenClient();

            if (_options.RequestPopTokens)
            {
                //-- Make sure the key is created
                _logger.LogTrace("CreateProviderForPopToken");
                var popKey = await(state.PopTokenGenerationTask ?? PopTokenExtensions.CreateProviderForPopTokenAsync()).ConfigureAwait(false);
                var jwk    = popKey.ToJwk();

                //-- Code request.
                _logger.LogTrace("Sending request");
                var tokenResult = await client.RequestAuthorizationCodePopAsync(
                    code,
                    state.RedirectUri,
                    state.CodeVerifier,
                    jwk.Alg,
                    jwk.ToJwkString()
                    ).ConfigureAwait(false);

                return(new Tuple <TokenResponse, RsaSecurityKey>(tokenResult, popKey));
            }
            else
            {
                //-- Code request.
                _logger.LogTrace("Sending request");
                var tokenResult = await client.RequestAuthorizationCodeAsync(
                    code,
                    state.RedirectUri,
                    state.CodeVerifier
                    ).ConfigureAwait(false);

                return(new Tuple <TokenResponse, RsaSecurityKey>(tokenResult, null));
            }
        }
        internal async Task <TokenResponseValidationResult> ValidateTokenResponseAsync(TokenResponse response, AuthorizeState state, bool requireIdentityToken = true)
        {
            _logger.LogTrace("ValidateTokenResponse");

            // token response must contain an access token
            if (response.AccessToken.IsMissing())
            {
                return(new TokenResponseValidationResult("Access token is missing on token response."));
            }

            if (requireIdentityToken)
            {
                // token response must contain an identity token (openid scope is mandatory)
                if (response.IdentityToken.IsMissing())
                {
                    return(new TokenResponseValidationResult("Identity token is missing on token response."));
                }
            }

            if (response.IdentityToken.IsPresent())
            {
                // if identity token is present, it must be valid
                var validationResult = await _tokenValidator.ValidateAsync(response.IdentityToken);

                if (validationResult.IsError)
                {
                    return(new TokenResponseValidationResult(validationResult.Error ?? "Identity token validation error"));
                }

                // validate nonce
                if (state != null)
                {
                    if (!ValidateNonce(state.Nonce, validationResult.User))
                    {
                        return(new TokenResponseValidationResult("Invalid nonce."));
                    }
                }

                // validate at_hash
                var atHash = validationResult.User.FindFirst(JwtClaimTypes.AccessTokenHash);
                if (atHash == null)
                {
                    if (_options.Policy.RequireAccessTokenHash)
                    {
                        return(new TokenResponseValidationResult("at_hash is missing."));
                    }
                }
                else
                {
                    if (!_crypto.ValidateHash(response.AccessToken, atHash.Value, validationResult.SignatureAlgorithm))
                    {
                        return(new TokenResponseValidationResult("Invalid access token hash."));
                    }
                }

                return(new TokenResponseValidationResult(validationResult));
            }

            return(new TokenResponseValidationResult((IdentityTokenValidationResult)null));
        }
Exemple #19
0
        private async Task <ResponseValidationResult> ProcessImplicitFlowResponseAsync(AuthorizeResponse authorizeResponse, AuthorizeState state, object extraParameters = null)
        {
            _logger.LogTrace("ProcessImplicitFlowResponseAsync");

            // id_token must be present
            if (authorizeResponse.IdentityToken.IsMissing())
            {
                return(new ResponseValidationResult("Missing identity token."));
            }

            // access token must be present
            if (authorizeResponse.AccessToken.IsMissing())
            {
                return(new ResponseValidationResult("Access token is missing on token response."));
            }

            // id_token must be valid
            var validationResult = await _tokenValidator.ValidateAsync(authorizeResponse.IdentityToken);

            if (validationResult.IsError)
            {
                return(new ResponseValidationResult(validationResult.Error ?? "Identity token validation error."));
            }

            // nonce must be valid
            if (!ValidateNonce(state.Nonce, validationResult.User))
            {
                return(new ResponseValidationResult("Invalid nonce."));
            }

            // validate at_hash
            var atHash = validationResult.User.FindFirst(JwtClaimTypes.AccessTokenHash);

            if (atHash == null)
            {
                if (_options.Policy.RequireAccessTokenHash)
                {
                    return(new ResponseValidationResult("at_hash is missing."));
                }
            }
            else
            {
                if (!_crypto.ValidateHash(authorizeResponse.AccessToken, atHash.Value, validationResult.SignatureAlgorithm))
                {
                    return(new ResponseValidationResult("Invalid access token hash."));
                }
            }

            return(new ResponseValidationResult
            {
                AuthorizeResponse = authorizeResponse,
                User = validationResult.User
            });
        }
        public async Task <ResponseValidationResult> ValidateHybridFlowResponseAsync(AuthorizeResponse authorizeResponse, AuthorizeState state)
        {
            Logger.Debug("Validate hybrid flow response");
            var result = new ResponseValidationResult();

            //////////////////////////////////////////////////////
            // validate front-channel response
            //////////////////////////////////////////////////////

            // id_token must be present
            if (authorizeResponse.IdentityToken.IsMissing())
            {
                result.Error = "Missing identity token";
                Logger.Error(result.Error);

                return(result);
            }

            // id_token must be valid
            var validationResult = await ValidateIdentityTokenAsync(authorizeResponse.IdentityToken);

            if (!validationResult.Success)
            {
                result.Error = validationResult.Error ?? "Identity token validation error";
                Logger.Error(result.Error);

                return(result);
            }

            // nonce must be valid
            if (!ValidateNonce(state.Nonce, validationResult.Claims))
            {
                result.Error = "Invalid nonce";
                Logger.Error(result.Error);

                return(result);
            }

            // if c_hash is present, it must be valid
            var signingAlgorithmBits = int.Parse(validationResult.SignatureAlgorithm.Substring(2));

            if (!ValidateAuthorizationCodeHash(authorizeResponse.Code, signingAlgorithmBits, validationResult.Claims))
            {
                result.Error = "Invalid c_hash";
                Logger.Error(result.Error);

                return(result);
            }

            //////////////////////////////////////////////////////
            // process back-channel response
            //////////////////////////////////////////////////////

            // redeem code for tokens
            var tokenResponse = await RedeemCodeAsync(authorizeResponse.Code, state);

            if (tokenResponse.IsError || tokenResponse.IsHttpError)
            {
                Logger.Error(result.Error);
                result.Error = tokenResponse.Error;
                return(result);
            }

            // validate token response
            var tokenResponseValidationResult = await ValidateTokenResponse(tokenResponse);

            if (!tokenResponseValidationResult.Success)
            {
                result.Error = tokenResponseValidationResult.Error;
                return(result);
            }

            return(new ResponseValidationResult
            {
                AuthorizeResponse = authorizeResponse,
                TokenResponse = tokenResponse,
                Claims = tokenResponseValidationResult.IdentityTokenValidationResult.Claims
            });
        }
        private async Task <ResponseValidationResult> ProcessHybridFlowResponseAsync(AuthorizeResponse authorizeResponse, AuthorizeState state, object extraParameters = null)
        {
            _logger.LogTrace("ProcessHybridFlowResponseAsync");

            //////////////////////////////////////////////////////
            // validate front-channel response
            //////////////////////////////////////////////////////

            // id_token must be present
            if (authorizeResponse.IdentityToken.IsMissing())
            {
                return(new ResponseValidationResult("Missing identity token."));
            }

            // id_token must be valid
            var frontChannelValidationResult = await _tokenValidator.ValidateAsync(authorizeResponse.IdentityToken);

            if (frontChannelValidationResult.IsError)
            {
                return(new ResponseValidationResult(frontChannelValidationResult.Error ?? "Identity token validation error."));
            }

            // nonce must be valid
            if (!ValidateNonce(state.Nonce, frontChannelValidationResult.User))
            {
                return(new ResponseValidationResult("Invalid nonce."));
            }

            // validate c_hash
            var cHash = frontChannelValidationResult.User.FindFirst(JwtClaimTypes.AuthorizationCodeHash);

            if (cHash == null)
            {
                if (_options.Policy.RequireAuthorizationCodeHash)
                {
                    return(new ResponseValidationResult("c_hash is missing."));
                }
            }
            else
            {
                if (!_crypto.ValidateHash(authorizeResponse.Code, cHash.Value, frontChannelValidationResult.SignatureAlgorithm))
                {
                    return(new ResponseValidationResult("Invalid c_hash."));
                }
            }

            //////////////////////////////////////////////////////
            // process back-channel response
            //////////////////////////////////////////////////////

            // redeem code for tokens
            var tokenResponse = await RedeemCodeAsync(authorizeResponse.Code, state, extraParameters);

            if (tokenResponse.IsError)
            {
                return(new ResponseValidationResult(tokenResponse.Error));
            }

            // validate token response
            var tokenResponseValidationResult = await ValidateTokenResponseAsync(tokenResponse, state);

            if (tokenResponseValidationResult.IsError)
            {
                return(new ResponseValidationResult(tokenResponseValidationResult.Error));
            }

            // compare front & back channel subs
            var frontChannelSub = frontChannelValidationResult.User.FindFirst(JwtClaimTypes.Subject).Value;
            var backChannelSub  = tokenResponseValidationResult.IdentityTokenValidationResult.User.FindFirst(JwtClaimTypes.Subject).Value;

            if (!string.Equals(frontChannelSub, backChannelSub, StringComparison.Ordinal))
            {
                return(new ResponseValidationResult($"Subject on front-channel ({frontChannelSub}) does not match subject on back-channel ({backChannelSub})."));
            }

            return(new ResponseValidationResult
            {
                AuthorizeResponse = authorizeResponse,
                TokenResponse = tokenResponse,
                User = tokenResponseValidationResult.IdentityTokenValidationResult.User
            });
        }
        private async Task <LoginResult> ValidateHybridFlowResponse(AuthorizeResponse authorizeResponse, AuthorizeState state)
        {
            var result = new LoginResult {
                Success = false
            };

            if (string.IsNullOrEmpty(authorizeResponse.IdentityToken))
            {
                result.Error = "missing identity token";
                return(result);
            }

            var validationResult = await ValidateIdentityTokenAsync(authorizeResponse.IdentityToken);

            if (!validationResult.Success)
            {
                result.Error = validationResult.Error ?? "identity token validation error";
                return(result);
            }

            if (!ValidateNonce(state.Nonce, validationResult.Claims))
            {
                result.Error = "invalid nonce";
                return(result);
            }

            if (!ValidateAuthorizationCodeHash(authorizeResponse.Code, validationResult.Claims))
            {
                result.Error = "invalid c_hash";
                return(result);
            }

            // redeem code for tokens
            var tokenResponse = await RedeemCodeAsync(authorizeResponse.Code, state);

            if (tokenResponse.IsError || tokenResponse.IsHttpError)
            {
                return(new LoginResult
                {
                    Success = false,
                    Error = tokenResponse.Error
                });
            }

            return(await ProcessClaims(authorizeResponse, tokenResponse, validationResult.Claims));
        }
        private async Task <LoginResult> ValidateCodeFlowResponse(AuthorizeResponse authorizeResponse, AuthorizeState state)
        {
            var result = new LoginResult {
                Success = false
            };

            // redeem code for tokens
            var tokenResponse = await RedeemCodeAsync(authorizeResponse.Code, state);

            if (tokenResponse.IsError || tokenResponse.IsHttpError)
            {
                result.Error = tokenResponse.Error;
                return(result);
            }

            if (tokenResponse.IdentityToken.IsMissing())
            {
                result.Error = "missing identity token";
                return(result);
            }

            var validationResult = await ValidateIdentityTokenAsync(tokenResponse.IdentityToken);

            if (!validationResult.Success)
            {
                result.Error = validationResult.Error ?? "identity token validation error";
                return(result);
            }

            if (!ValidateAccessTokenHash(authorizeResponse.AccessToken, validationResult.Claims))
            {
                result.Error = "invalid access token hash";
                return(result);
            }

            return(await ProcessClaims(authorizeResponse, tokenResponse, validationResult.Claims));
        }
        private async Task <ResponseValidationResult> ProcessCodeFlowResponseAsync(AuthorizeResponse authorizeResponse, AuthorizeState state, object extraParameters)
        {
            _logger.LogTrace("ProcessCodeFlowResponseAsync");

            //////////////////////////////////////////////////////
            // process back-channel response
            //////////////////////////////////////////////////////

            // redeem code for tokens
            var tokenResponse = await RedeemCodeAsync(authorizeResponse.Code, state, extraParameters);

            if (tokenResponse.IsError)
            {
                return(new ResponseValidationResult($"Error redeeming code: {tokenResponse.Error ?? "no error code"} / {tokenResponse.ErrorDescription ?? "no description"}"));
            }

            // validate token response
            var tokenResponseValidationResult = await ValidateTokenResponseAsync(tokenResponse, state);

            if (tokenResponseValidationResult.IsError)
            {
                return(new ResponseValidationResult($"Error validating token response: {tokenResponseValidationResult.Error}"));
            }

            return(new ResponseValidationResult
            {
                AuthorizeResponse = authorizeResponse,
                TokenResponse = tokenResponse,
                User = tokenResponseValidationResult.IdentityTokenValidationResult.User
            });
        }
Exemple #25
0
        public async Task <LoginResult> ValidateResponseAsync(string data, AuthorizeState state)
        {
            var result = new LoginResult {
                Success = false
            };
            var response = new AuthorizeResponse(data);

            if (response.IsError)
            {
                result.Error = response.Error;
                return(result);
            }

            if (string.IsNullOrEmpty(response.Code))
            {
                result.Error = "Missing authorization code";
                return(result);
            }

            if (string.IsNullOrEmpty(response.IdentityToken))
            {
                result.Error = "Missing identity token";
                return(result);
            }

            // validate identity token signature
            var providerInfo = await _options.GetProviderInformationAsync();

            var validationResult = await _options.IdentityTokenValidator.ValidateAsync(response.IdentityToken, _options.ClientId, providerInfo);

            if (validationResult.Success == false)
            {
                return(new LoginResult
                {
                    Success = false,
                    Error = validationResult.Error ?? "identity token validation error"
                });
            }

            var claims = validationResult.Claims;

            // validate audience
            var audience = claims.FindFirst(JwtClaimTypes.Audience)?.Value ?? "";

            if (!string.Equals(_options.ClientId, audience))
            {
                return(new LoginResult
                {
                    Success = false,
                    Error = "invalid audience"
                });
            }

            // validate issuer
            var issuer = claims.FindFirst(JwtClaimTypes.Issuer)?.Value ?? "";

            if (!string.Equals(providerInfo.IssuerName, issuer))
            {
                return(new LoginResult
                {
                    Success = false,
                    Error = "invalid issuer"
                });
            }

            // validate nonce
            var tokenNonce = claims.FindFirst(JwtClaimTypes.Nonce)?.Value ?? "";

            if (!string.Equals(state.Nonce, tokenNonce))
            {
                return(new LoginResult
                {
                    Success = false,
                    Error = "invalid nonce"
                });
            }

            // validate c_hash
            var cHash  = claims.FindFirst(JwtClaimTypes.AuthorizationCodeHash)?.Value ?? "";
            var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256);

            var codeHash = sha256.HashData(
                CryptographicBuffer.CreateFromByteArray(
                    Encoding.UTF8.GetBytes(response.Code)));

            byte[] codeHashArray;
            CryptographicBuffer.CopyToByteArray(codeHash, out codeHashArray);

            byte[] leftPart = new byte[16];
            Array.Copy(codeHashArray, leftPart, 16);

            var leftPartB64 = Base64Url.Encode(leftPart);

            if (!leftPartB64.Equals(cHash))
            {
                return(new LoginResult
                {
                    Success = false,
                    Error = "invalid code"
                });
            }

            // redeem code for tokens
            var tokenResult = await RedeemCodeAsync(response.Code, state);

            if (tokenResult.IsError || tokenResult.IsHttpError)
            {
                return(new LoginResult
                {
                    Success = false,
                    Error = tokenResult.Error
                });
            }

            // get profile if enabled
            if (_options.LoadProfile)
            {
                var userInfoResult = await GetUserInfoAsync(tokenResult.AccessToken);

                if (!userInfoResult.Success)
                {
                    return(new LoginResult
                    {
                        Success = false,
                        Error = userInfoResult.Error
                    });
                }

                var primaryClaimTypes = claims.Select(c => c.Type).Distinct();
                foreach (var claim in userInfoResult.Claims.Where(c => !primaryClaimTypes.Contains(c.Type)))
                {
                    claims.Add(claim);
                }
            }

            // success
            var loginResult = new LoginResult
            {
                Success               = true,
                Claims                = FilterClaims(claims),
                AccessToken           = tokenResult.AccessToken,
                RefreshToken          = tokenResult.RefreshToken,
                AccessTokenExpiration = DateTime.Now.AddSeconds(tokenResult.ExpiresIn),
                IdentityToken         = response.IdentityToken,
                AuthenticationTime    = DateTime.Now,
            };

            if (!string.IsNullOrWhiteSpace(tokenResult.RefreshToken))
            {
                loginResult.Handler = new RefeshTokenHandler(
                    providerInfo.TokenEndpoint,
                    _options.ClientId,
                    _options.ClientSecret,
                    tokenResult.RefreshToken,
                    tokenResult.AccessToken);
            }

            return(loginResult);
        }
        public async Task <ResponseValidationResult> ProcessResponseAsync(AuthorizeResponse authorizeResponse, AuthorizeState state, object extraParameters)
        {
            _logger.LogTrace("ProcessResponseAsync");

            //////////////////////////////////////////////////////
            // validate common front-channel parameters
            //////////////////////////////////////////////////////

            if (string.IsNullOrEmpty(authorizeResponse.Code))
            {
                return(new ResponseValidationResult("Missing authorization code."));
            }

            if (string.IsNullOrEmpty(authorizeResponse.State))
            {
                return(new ResponseValidationResult("Missing state."));
            }

            if (!string.Equals(state.State, authorizeResponse.State, StringComparison.Ordinal))
            {
                return(new ResponseValidationResult("Invalid state."));
            }

            switch (_options.Flow)
            {
            case OidcClientOptions.AuthenticationFlow.AuthorizationCode:
                return(await ProcessCodeFlowResponseAsync(authorizeResponse, state, extraParameters));

            case OidcClientOptions.AuthenticationFlow.Hybrid:
                return(await ProcessHybridFlowResponseAsync(authorizeResponse, state, extraParameters));

            default:
                throw new ArgumentOutOfRangeException(nameof(_options.Flow), "Invalid authentication style.");
            }
        }
        /// <summary>
        /// Validates the response.
        /// </summary>
        /// <param name="data">The response data.</param>
        /// <param name="state">The state.</param>
        /// <returns>Result of the login response validation</returns>
        /// <exception cref="System.InvalidOperationException">Invalid authentication style</exception>
        public async Task <LoginResult> ValidateResponseAsync(string data, AuthorizeState state)
        {
            Logger.Debug("Validate authorize response");

            var response = new AuthorizeResponse(data);

            if (response.IsError)
            {
                Logger.Error(response.Error);

                return(new LoginResult(response.Error));
            }

            if (string.IsNullOrEmpty(response.Code))
            {
                var error = "Missing authorization code";
                Logger.Error(error);

                return(new LoginResult(error));
            }

            if (string.IsNullOrEmpty(response.State))
            {
                var error = "Missing state";
                Logger.Error(error);

                return(new LoginResult(error));
            }

            if (!string.Equals(state.State, response.State, StringComparison.Ordinal))
            {
                var error = "Invalid state";
                Logger.Error(error);

                return(new LoginResult(error));
            }

            ResponseValidationResult validationResult = null;

            if (_options.Style == OidcClientOptions.AuthenticationStyle.AuthorizationCode)
            {
                validationResult = await _validator.ValidateCodeFlowResponseAsync(response, state);
            }
            else if (_options.Style == OidcClientOptions.AuthenticationStyle.Hybrid)
            {
                validationResult = await _validator.ValidateHybridFlowResponseAsync(response, state);
            }
            else
            {
                throw new InvalidOperationException("Invalid authentication style");
            }

            if (!validationResult.Success)
            {
                Logger.Error("Error validating response: " + validationResult.Error);

                return(new LoginResult
                {
                    Error = validationResult.Error
                });
            }

            return(await ProcessClaimsAsync(validationResult));
        }
        /// <summary>
        /// Processes the authorize response.
        /// </summary>
        /// <param name="data">The response data.</param>
        /// <param name="state">The state.</param>
        /// <param name="backChannelParameters">Parameters for back-channel call</param>
        /// <param name="cancellationToken">A token that can be used to cancel the request</param>
        /// <returns>
        /// Result of the login response validation
        /// </returns>
        public virtual async Task <LoginResult> ProcessResponseAsync(
            string data,
            AuthorizeState state,
            BackChannelParameters backChannelParameters = null,
            CancellationToken cancellationToken         = default)
        {
            _logger.LogTrace("ProcessResponseAsync");
            _logger.LogInformation("Processing response.");

            backChannelParameters = backChannelParameters ?? new BackChannelParameters();
            await EnsureConfigurationAsync(cancellationToken);

            _logger.LogDebug("Authorize response: {response}", data);
            var authorizeResponse = new AuthorizeResponse(data);

            if (authorizeResponse.IsError)
            {
                _logger.LogError(authorizeResponse.Error);
                return(new LoginResult(authorizeResponse.Error, authorizeResponse.ErrorDescription));
            }

            var result = await _processor.ProcessResponseAsync(authorizeResponse, state, backChannelParameters, cancellationToken);

            if (result.IsError)
            {
                _logger.LogError(result.Error);
                return(new LoginResult(result.Error, result.ErrorDescription));
            }

            var userInfoClaims = Enumerable.Empty <Claim>();

            if (Options.LoadProfile)
            {
                var userInfoResult = await GetUserInfoAsync(result.TokenResponse.AccessToken, cancellationToken);

                if (userInfoResult.IsError)
                {
                    var error = $"Error contacting userinfo endpoint: {userInfoResult.Error}";
                    _logger.LogError(error);

                    return(new LoginResult(error));
                }

                userInfoClaims = userInfoResult.Claims;

                var userInfoSub = userInfoClaims.FirstOrDefault(c => c.Type == JwtClaimTypes.Subject);
                if (userInfoSub == null)
                {
                    var error = "sub claim is missing from userinfo endpoint";
                    _logger.LogError(error);

                    return(new LoginResult(error));
                }

                if (result.TokenResponse.IdentityToken != null)
                {
                    if (!string.Equals(userInfoSub.Value, result.User.FindFirst(JwtClaimTypes.Subject).Value))
                    {
                        var error = "sub claim from userinfo endpoint is different than sub claim from identity token.";
                        _logger.LogError(error);

                        return(new LoginResult(error));
                    }
                }
            }

            var user = ProcessClaims(result.User, userInfoClaims);

            var loginResult = new LoginResult
            {
                User                  = user,
                AccessToken           = result.TokenResponse.AccessToken,
                RefreshToken          = result.TokenResponse.RefreshToken,
                AccessTokenExpiration = DateTimeOffset.Now.AddSeconds(result.TokenResponse.ExpiresIn),
                IdentityToken         = result.TokenResponse.IdentityToken,
                AuthenticationTime    = DateTimeOffset.Now,
                TokenResponse         = result.TokenResponse // In some cases there is additional custom response data that clients need access to
            };

            if (loginResult.RefreshToken.IsPresent())
            {
                loginResult.RefreshTokenHandler = new RefreshTokenDelegatingHandler(
                    this,
                    loginResult.AccessToken,
                    loginResult.RefreshToken,
                    Options.RefreshTokenInnerHttpHandler);
            }

            return(loginResult);
        }
        public async Task <ResponseValidationResult> ValidateCodeFlowResponseAsync(AuthorizeResponse authorizeResponse, AuthorizeState state)
        {
            Logger.Debug("Validate code flow response");
            var result = new ResponseValidationResult();

            //////////////////////////////////////////////////////
            // validate front-channel response
            //////////////////////////////////////////////////////

            // code must be present
            if (authorizeResponse.Code.IsMissing())
            {
                result.Error = "code is missing";
                Logger.Error(result.Error);

                return(result);
            }

            if (!string.Equals(authorizeResponse.State, state.State, StringComparison.Ordinal))
            {
                result.Error = "invalid state";
                Logger.Error(result.Error);

                return(result);
            }

            //////////////////////////////////////////////////////
            // process back-channel response
            //////////////////////////////////////////////////////

            // redeem code for tokens
            var tokenResponse = await RedeemCodeAsync(authorizeResponse.Code, state);

            if (tokenResponse.IsError || tokenResponse.IsHttpError)
            {
                result.Error = tokenResponse.Error;
                return(result);
            }

            // validate token response
            var tokenResponseValidationResult = await ValidateTokenResponse(tokenResponse);

            if (!tokenResponseValidationResult.Success)
            {
                result.Error = tokenResponseValidationResult.Error;
                return(result);
            }

            return(new ResponseValidationResult
            {
                AuthorizeResponse = authorizeResponse,
                TokenResponse = tokenResponse,
                Claims = tokenResponseValidationResult.IdentityTokenValidationResult.Claims
            });
        }