private async Task <List <Claim> > ValidateAccessTokenAsync(TParty party, OidcUpSequenceData sequenceData, string accessToken) { try { var jwtToken = JwtHandler.ReadToken(accessToken); var issuer = party.Issuers.Where(i => i == jwtToken.Issuer).FirstOrDefault(); if (string.IsNullOrEmpty(issuer)) { throw new OAuthRequestException($"{party.Name}|Access token issuer '{jwtToken.Issuer}' is unknown.") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken }; } var claimsPrincipal = await jwtUpLogic.ValidateAccessTokenAsync(accessToken, issuer, party, sequenceData.ClientId); return(claimsPrincipal.Claims.ToList()); } catch (OAuthRequestException) { throw; } catch (Exception ex) { throw new OAuthRequestException($"{party.Name}|Access token not valid.", ex) { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken }; } }
private async Task <IActionResult> AuthenticationResponseDownAsync(OidcUpSequenceData sequenceData, List <Claim> claims = null, string error = null, string errorDescription = null) { try { logger.ScopeTrace(() => $"Response, Down type {sequenceData.DownPartyLink.Type}."); switch (sequenceData.DownPartyLink.Type) { case PartyTypes.OAuth2: throw new NotImplementedException(); case PartyTypes.Oidc: if (error.IsNullOrEmpty()) { return(await serviceProvider.GetService <OidcAuthDownLogic <OidcDownParty, OidcDownClient, OidcDownScope, OidcDownClaim> >().AuthenticationResponseAsync(sequenceData.DownPartyLink.Id, claims)); } else { return(await serviceProvider.GetService <OidcAuthDownLogic <OidcDownParty, OidcDownClient, OidcDownScope, OidcDownClaim> >().AuthenticationResponseErrorAsync(sequenceData.DownPartyLink.Id, error, errorDescription)); } case PartyTypes.Saml2: var claimsLogic = serviceProvider.GetService <ClaimsDownLogic <OidcDownClient, OidcDownScope, OidcDownClaim> >(); return(await serviceProvider.GetService <SamlAuthnDownLogic>().AuthnResponseAsync(sequenceData.DownPartyLink.Id, ErrorToSamlStatus(error), claims != null ? await claimsLogic.FromJwtToSamlClaimsAsync(claims) : null)); default: throw new NotSupportedException(); } } catch (Exception ex) { throw new StopSequenceException("Falling authentication response down", ex); } }
private async Task <IActionResult> LogoutResponseDownAsync(OidcUpSequenceData sequenceData) { try { logger.ScopeTrace(() => $"Response, Down type {sequenceData.DownPartyLink.Type}."); switch (sequenceData.DownPartyLink.Type) { case PartyTypes.OAuth2: throw new NotImplementedException(); case PartyTypes.Oidc: return(await serviceProvider.GetService <OidcRpInitiatedLogoutDownLogic <OidcDownParty, OidcDownClient, OidcDownScope, OidcDownClaim> >().EndSessionResponseAsync(sequenceData.DownPartyLink.Id)); case PartyTypes.Saml2: return(await serviceProvider.GetService <SamlLogoutDownLogic>().LogoutResponseAsync(sequenceData.DownPartyLink.Id, sessionIndex: sequenceData.SessionId)); default: throw new NotSupportedException(); } } catch (Exception ex) { throw new StopSequenceException("Falling logout response down", ex); } }
private async Task <List <Claim> > ValidateIdTokenAsync(TParty party, OidcUpSequenceData sequenceData, string idToken, string accessToken, bool authorizationEndpoint) { try { var jwtToken = JwtHandler.ReadToken(idToken); var issuer = party.Issuers.Where(i => i == jwtToken.Issuer).FirstOrDefault(); if (string.IsNullOrEmpty(issuer)) { throw new OAuthRequestException($"{party.Name}|Id token issuer '{jwtToken.Issuer}' is unknown.") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken }; } var claimsPrincipal = await jwtUpLogic.ValidateIdTokenAsync(idToken, issuer, party, sequenceData.ClientId); var nonce = claimsPrincipal.Claims.FindFirstValue(c => c.Type == JwtClaimTypes.Nonce); if (!sequenceData.Nonce.Equals(nonce, StringComparison.Ordinal)) { throw new OAuthRequestException($"{party.Name}|Id token nonce do not match.") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken }; } if (authorizationEndpoint && !accessToken.IsNullOrEmpty()) { var atHash = claimsPrincipal.Claims.FindFirstValue(c => c.Type == JwtClaimTypes.AtHash); string algorithm = IdentityConstants.Algorithms.Asymmetric.RS256; if (atHash != await accessToken.LeftMostBase64urlEncodedHashAsync(algorithm)) { throw new OAuthRequestException($"{party.Name}|Access Token hash claim in ID token do not match the access token.") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken }; } } return(claimsPrincipal.Claims.ToList()); } catch (OAuthRequestException) { throw; } catch (Exception ex) { throw new OAuthRequestException($"{party.Name}|Id token not valid.", ex) { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken }; } }
public async Task <IActionResult> AuthenticationRequestRedirectAsync(UpPartyLink partyLink, LoginRequest loginRequest) { logger.ScopeTrace("Up, OIDC Authentication request redirect."); var partyId = await UpParty.IdFormatAsync(RouteBinding, partyLink.Name); logger.SetScopeProperty("upPartyId", partyId); await loginRequest.ValidateObjectAsync(); var oidcUpSequenceData = new OidcUpSequenceData { DownPartyLink = loginRequest.DownPartyLink, UpPartyId = partyId, LoginAction = loginRequest.LoginAction, UserId = loginRequest.UserId, MaxAge = loginRequest.MaxAge }; await sequenceLogic.SaveSequenceDataAsync(oidcUpSequenceData); return(HttpContext.GetUpPartyUrl(partyLink.Name, Constants.Routes.OAuthUpJumpController, Constants.Endpoints.UpJump.AuthenticationRequest, includeSequence: true).ToRedirectResult()); }
private async Task <(List <Claim>, string)> ValidateTokensAsync(TParty party, OidcUpSequenceData sequenceData, string idToken, string accessToken, bool authorizationEndpoint) { var claims = await ValidateIdTokenAsync(party, sequenceData, idToken, accessToken, authorizationEndpoint); if (!accessToken.IsNullOrWhiteSpace()) { if (!party.Client.UseIdTokenClaims) { var sessionIdClaim = claims.Where(c => c.Type == JwtClaimTypes.SessionId).FirstOrDefault(); claims = await ValidateAccessTokenAsync(party, sequenceData, accessToken); if (sessionIdClaim != null && !claims.Where(c => c.Type == JwtClaimTypes.SessionId).Any()) { claims.Add(sessionIdClaim); } } claims.AddClaim(Constants.JwtClaimTypes.AccessToken, $"{party.Name}|{accessToken}"); } var subject = claims.FindFirstValue(c => c.Type == JwtClaimTypes.Subject); if (!subject.IsNullOrEmpty()) { claims = claims.Where(c => c.Type != JwtClaimTypes.Subject).ToList(); claims.Add(new Claim(JwtClaimTypes.Subject, $"{party.Name}|{subject}")); } return(claims, idToken); }
private async Task <TokenResponse> TokenRequestAsync(TClient client, string code, OidcUpSequenceData sequenceData) { var tokenRequest = new TokenRequest { GrantType = IdentityConstants.GrantTypes.AuthorizationCode, Code = code, ClientId = !client.SpClientId.IsNullOrWhiteSpace() ? client.SpClientId : client.ClientId, RedirectUri = sequenceData.RedirectUri, }; logger.ScopeTrace(() => $"Up, Token request '{tokenRequest.ToJsonIndented()}'.", traceType: TraceTypes.Message); var requestDictionary = tokenRequest.ToDictionary(); if (!client.ClientSecret.IsNullOrEmpty()) { var clientCredentials = new ClientCredentials { ClientSecret = client.ClientSecret, }; logger.ScopeTrace(() => $"Up, Client credentials '{new ClientCredentials { ClientSecret = $"{(clientCredentials.ClientSecret?.Length > 10 ? clientCredentials.ClientSecret.Substring(0, 3) : string.Empty)}..." }.ToJsonIndented()}'.", traceType: TraceTypes.Message); requestDictionary = requestDictionary.AddToDictionary(clientCredentials); } if (client.EnablePkce) { var codeVerifierSecret = new CodeVerifierSecret { CodeVerifier = sequenceData.CodeVerifier, }; logger.ScopeTrace(() => $"Up, Code verifier secret '{new CodeVerifierSecret { CodeVerifier = $"{(codeVerifierSecret.CodeVerifier?.Length > 10 ? codeVerifierSecret.CodeVerifier.Substring(0, 3) : string.Empty)}..." }.ToJsonIndented()}'.", traceType: TraceTypes.Message); requestDictionary = requestDictionary.AddToDictionary(codeVerifierSecret); } logger.ScopeTrace(() => $"Up, OIDC Token request URL '{client.TokenUrl}'.", traceType: TraceTypes.Message); var request = new HttpRequestMessage(HttpMethod.Post, client.TokenUrl); request.Content = new FormUrlEncodedContent(requestDictionary); using var response = await httpClientFactory.CreateClient(nameof (HttpClient)).SendAsync(request); switch (response.StatusCode) { case HttpStatusCode.OK: var result = await response.Content.ReadAsStringAsync(); var tokenResponse = result.ToObject <TokenResponse>(); logger.ScopeTrace(() => $"Up, Token response '{tokenResponse.ToJsonIndented()}'.", traceType: TraceTypes.Message); tokenResponse.Validate(true); if (tokenResponse.AccessToken.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(tokenResponse.AccessToken), tokenResponse.GetTypeName()); } if (tokenResponse.ExpiresIn <= 0) { throw new ArgumentNullException(nameof(tokenResponse.ExpiresIn), tokenResponse.GetTypeName()); } return(tokenResponse); case HttpStatusCode.BadRequest: var resultBadRequest = await response.Content.ReadAsStringAsync(); var tokenResponseBadRequest = resultBadRequest.ToObject <TokenResponse>(); logger.ScopeTrace(() => $"Up, Bad token response '{tokenResponseBadRequest.ToJsonIndented()}'.", traceType: TraceTypes.Message); try { tokenResponseBadRequest.Validate(true); } catch (ResponseErrorException rex) { throw new OAuthRequestException($"External {rex.Message}") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidRequest }; } throw new EndpointException($"Bad request. Status code '{response.StatusCode}'. Response '{resultBadRequest}'.") { RouteBinding = RouteBinding }; default: try { var resultUnexpectedStatus = await response.Content.ReadAsStringAsync(); var tokenResponseUnexpectedStatus = resultUnexpectedStatus.ToObject <TokenResponse>(); logger.ScopeTrace(() => $"Up, Unexpected status code response '{tokenResponseUnexpectedStatus.ToJsonIndented()}'.", traceType: TraceTypes.Message); try { tokenResponseUnexpectedStatus.Validate(true); } catch (ResponseErrorException rex) { throw new OAuthRequestException($"External {rex.Message}") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidRequest }; } } catch (OAuthRequestException) { throw; } catch (Exception ex) { throw new EndpointException($"Unexpected status code. Status code={response.StatusCode}", ex) { RouteBinding = RouteBinding }; } throw new EndpointException($"Unexpected status code. Status code={response.StatusCode}") { RouteBinding = RouteBinding }; } }
private async Task <(List <Claim>, string)> HandleAuthorizationCodeResponseAsync(TParty party, OidcUpSequenceData sequenceData, string code) { var tokenResponse = await TokenRequestAsync(party.Client, code, sequenceData); return(await ValidateTokensAsync(party, sequenceData, tokenResponse.IdToken, tokenResponse.AccessToken, false)); }
private async Task <(List <Claim>, string)> ValidateTokensAsync(OidcUpParty party, OidcUpSequenceData sequenceData, string idToken, string accessToken, bool authorizationEndpoint) { var claims = await ValidateIdTokenAsync(party, sequenceData, idToken, accessToken, authorizationEndpoint); if (!accessToken.IsNullOrWhiteSpace()) { if (!party.Client.UseIdTokenClaims) { // If access token exists, use access token claims instead of ID token claims. claims = ValidateAccessToken(party, sequenceData, accessToken); } claims.AddClaim(Constants.JwtClaimTypes.AccessToken, $"{party.Name}|{accessToken}"); } var subject = claims.FindFirstValue(c => c.Type == JwtClaimTypes.Subject); if (!subject.IsNullOrEmpty()) { claims = claims.Where(c => c.Type != JwtClaimTypes.Subject).ToList(); claims.Add(new Claim(JwtClaimTypes.Subject, $"{party.Name}|{subject}")); } return(await Task.FromResult((claims, idToken))); }