/// <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); }
/// <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); }
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); }