Example #1
0
        /// <summary>
        /// Processes the authorize response.
        /// </summary>
        /// <param name="data">The response data.</param>
        /// <param name="state">The state.</param>
        /// <param name="extraParameters">The extra parameters.</param>
        /// <returns>
        /// Result of the login response validation
        /// </returns>
        public virtual async Task <LoginResult> ProcessResponseAsync(string data, AuthorizeState state, object extraParameters = null)
        {
            _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, extraParameters);

            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,
                    _options.RefreshTokenInnerHttpHandler);
            }

            return(loginResult);
        }
Example #2
0
        /// <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,
            Parameters backChannelParameters    = null,
            CancellationToken cancellationToken = default)
        {
            _logger.LogTrace("ProcessResponseAsync");
            _logger.LogInformation("Processing response.");

            backChannelParameters = backChannelParameters ?? new Parameters();
            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            authTimeValue = result.TokenResponse.TryGet(JwtClaimTypes.AuthenticationTime);
            DateTimeOffset?authTime      = null;

            if (authTimeValue.IsPresent() && long.TryParse(authTimeValue, out long seconds))
            {
                authTime = DateTimeOffset.FromUnixTimeSeconds(seconds);
            }

            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    = authTime,
                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);
        }
Example #3
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);
        }