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