public async Task PopulateModelAsync(OidcUpParty party) { (var oidcDiscovery, var jsonWebKeySet) = await GetOidcDiscoveryAndValidateAsync(party.Authority); party.LastUpdated = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); if (party.EditIssuersInAutomatic != true || string.IsNullOrWhiteSpace(party.Issuers?.FirstOrDefault())) { party.Issuers = new List <string> { oidcDiscovery.Issuer }; } party.Client.AuthorizeUrl = oidcDiscovery.AuthorizationEndpoint; if (!oidcDiscovery.TokenEndpoint.IsNullOrEmpty()) { party.Client.TokenUrl = oidcDiscovery.TokenEndpoint; } else if (party.Client.ResponseType?.Contains(IdentityConstants.ResponseTypes.Code) == true) { party.Client.ResponseType = $"{IdentityConstants.ResponseTypes.Token} {IdentityConstants.ResponseTypes.IdToken}"; party.Client.EnablePkce = false; } if (!oidcDiscovery.EndSessionEndpoint.IsNullOrEmpty()) { party.Client.EndSessionUrl = oidcDiscovery.EndSessionEndpoint; } party.Keys = jsonWebKeySet.Keys?.ToList(); }
private List <Claim> ValidateAccessToken(OidcUpParty 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, _) = JwtHandler.ValidateToken(accessToken, issuer, party.Keys, sequenceData.ClientId, validateAudience: false); 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 }; } }
public async Task <bool> PopulateModelAsync(ModelStateDictionary modelState, OidcUpParty mp) { var isValid = true; try { if (mp.UpdateState != PartyUpdateStates.Manual) { await oidcDiscoveryReadLogic.PopulateModelAsync(mp); if (mp.UpdateState == PartyUpdateStates.AutomaticStopped) { mp.UpdateState = PartyUpdateStates.Automatic; } if (mp.EditIssuersInAutomatic == false) { mp.EditIssuersInAutomatic = null; } } else { mp.EditIssuersInAutomatic = null; } } catch (Exception ex) { isValid = false; logger.Warning(ex); modelState.TryAddModelError(nameof(mp.Authority).ToCamelCase(), ex.GetAllMessagesJoined()); } return(isValid); }
private void ValidatePartyLogoutSupport(OidcUpParty party) { if (party.Client.EndSessionUrl.IsNullOrEmpty()) { throw new EndpointException("End session not configured.") { RouteBinding = RouteBinding }; } }
private async Task <List <Claim> > ValidateIdTokenAsync(OidcUpParty 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, _) = JwtHandler.ValidateToken(idToken, issuer, party.Keys, 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 }; } }
private async Task CreateIdentityserverOidcOpUpPartyAsync() { Func <string, Task> getAction = async(name) => { _ = await foxIDsApiClient.GetOidcUpPartyAsync(name); }; Func <string, Task> postAction = async(name) => { var baseUrl = "https://localhost:44346"; var key = File.ReadAllText("identityserver-tempkey.jwk").ToObject <JsonWebKey>(); var oidcUpParty = new OidcUpParty { Name = name, Authority = baseUrl, UpdateState = PartyUpdateStates.Manual, Issuers = new string[] { baseUrl }, Keys = new JsonWebKey[] { key }, Client = new OidcUpClient { AuthorizeUrl = $"{baseUrl}/connect/authorize", TokenUrl = $"{baseUrl}/connect/token", EndSessionUrl = $"{baseUrl}/connect/endsession", // A less secure configuration to enable local testing ResponseType = IdentityConstants.ResponseTypes.IdToken, // A more secure configuration //ResponseType = IdentityConstants.ResponseTypes.Code, //EnablePkce = true, //ClientSecret = "2tqjW-KwiGaR4KRt0IJ8KAJYw3pyPTK8S_dr_YE5nbw", Scopes = new string[] { "profile", "email" }, Claims = new string[] { "access_token", JwtClaimTypes.Email, JwtClaimTypes.EmailVerified, JwtClaimTypes.FamilyName, JwtClaimTypes.GivenName, JwtClaimTypes.Name, JwtClaimTypes.Role }, UseIdTokenClaims = true, ResponseMode = IdentityConstants.ResponseModes.FormPost } }; await foxIDsApiClient.PostOidcUpPartyAsync(oidcUpParty); }; await CreateIfNotExistsAsync(identityserverOidcOpUpPartyName, getAction, postAction); }
public async Task <bool> PopulateModelAsync(ModelStateDictionary modelState, OidcUpParty mp) { var isValid = true; try { if (mp.UpdateState != PartyUpdateStates.Manual) { (var oidcDiscovery, var jsonWebKeySet) = await oidcDiscoveryReadLogic.GetOidcDiscoveryAndValidateAsync(mp.Authority); mp.LastUpdated = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); if (mp.EditIssuersInAutomatic != true || string.IsNullOrWhiteSpace(mp.Issuers?.FirstOrDefault())) { mp.Issuers = new List <string> { oidcDiscovery.Issuer }; } mp.Client.AuthorizeUrl = oidcDiscovery.AuthorizationEndpoint; mp.Client.TokenUrl = oidcDiscovery.TokenEndpoint; if (!oidcDiscovery.EndSessionEndpoint.IsNullOrEmpty()) { mp.Client.EndSessionUrl = oidcDiscovery.EndSessionEndpoint; } mp.Keys = jsonWebKeySet.Keys?.ToList(); if (mp.UpdateState == PartyUpdateStates.AutomaticStopped) { mp.UpdateState = PartyUpdateStates.Automatic; } if (mp.EditIssuersInAutomatic == false) { mp.EditIssuersInAutomatic = null; } } else { mp.EditIssuersInAutomatic = null; } } catch (Exception ex) { isValid = false; logger.Warning(ex); modelState.TryAddModelError(nameof(mp.Authority).ToCamelCase(), ex.GetAllMessagesJoined()); } return(isValid); }
private void ValidateAuthenticationResponse(OidcUpParty party, AuthenticationResponse authenticationResponse, SessionResponse sessionResponse, bool isImplicitFlow) { authenticationResponse.Validate(isImplicitFlow); if (party.Client.EnablePkce && isImplicitFlow) { throw new OAuthRequestException($"Require '{IdentityConstants.ResponseTypes.Code}' flow with PKCE.") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidRequest }; } if (sessionResponse?.SessionState.IsNullOrEmpty() == false) { sessionResponse.Validate(); } }
private async Task <IActionResult> EndSessionResponseAsync(OidcUpParty party) { var queryDictionary = HttpContext.Request.Query.ToDictionary(); var rpInitiatedLogoutResponse = queryDictionary.ToObject <RpInitiatedLogoutResponse>(); logger.ScopeTrace(() => $"Up, End session response '{rpInitiatedLogoutResponse.ToJsonIndented()}'.", traceType: TraceTypes.Message); rpInitiatedLogoutResponse.Validate(); if (rpInitiatedLogoutResponse.State.IsNullOrEmpty()) { throw new ArgumentNullException(nameof(rpInitiatedLogoutResponse.State), rpInitiatedLogoutResponse.GetTypeName()); } await sequenceLogic.ValidateExternalSequenceIdAsync(rpInitiatedLogoutResponse.State); var sequenceData = await sequenceLogic.GetSequenceDataAsync <OidcUpSequenceData>(remove : party.DisableSingleLogout); logger.ScopeTrace(() => "Up, Successful OIDC End session response.", triggerEvent: true); if (party.DisableSingleLogout) { return(await LogoutResponseDownAsync(sequenceData)); } else { (var doSingleLogout, var singleLogoutSequenceData) = await singleLogoutDownLogic.InitializeSingleLogoutAsync(new UpPartyLink { Name = party.Name, Type = party.Type }, sequenceData.DownPartyLink, sequenceData.SessionDownPartyLinks, sequenceData.SessionClaims); if (doSingleLogout) { return(await singleLogoutDownLogic.StartSingleLogoutAsync(singleLogoutSequenceData)); } else { await sequenceLogic.RemoveSequenceDataAsync <OidcUpSequenceData>(); return(await LogoutResponseDownAsync(sequenceData)); } } }
private List <Claim> ValidateClaims(OidcUpParty party, List <Claim> claims) { var acceptedClaims = Constants.DefaultClaims.JwtTokenUpParty.ConcatOnce(party.Client.Claims).Where(c => !Constants.DefaultClaims.ExcludeJwtTokenUpParty.Contains(c)); claims = claims.Where(c => acceptedClaims.Any(ic => ic == c.Type)).ToList(); foreach (var claim in claims) { if (claim.Type?.Length > Constants.Models.Claim.JwtTypeLength) { throw new OAuthRequestException($"Claim '{claim.Type.Substring(0, Constants.Models.Claim.JwtTypeLength)}' is too long, maximum length of '{Constants.Models.Claim.JwtTypeLength}'.") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken }; } if (Constants.EmbeddedJwtToken.JwtTokenClaims.Contains(claim.Type)) { if (claim.Value?.Length > Constants.EmbeddedJwtToken.ValueLength) { throw new OAuthRequestException($"Claim '{claim.Type}' value is too long, maximum length of '{Constants.EmbeddedJwtToken.ValueLength}'.") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken }; } } else { if (claim.Value?.Length > Constants.Models.Claim.ValueLength) { throw new OAuthRequestException($"Claim '{claim.Type}' value is too long, maximum length of '{Constants.Models.Claim.ValueLength}'.") { RouteBinding = RouteBinding, Error = IdentityConstants.ResponseErrors.InvalidToken }; } } } return(claims); }
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))); }
public async Task CheckOidcDiscoveryAndUpdatePartyAsync(OidcUpParty party) { if (party.UpdateState != PartyUpdateStates.Automatic) { return; } var lastUpdated = DateTimeOffset.FromUnixTimeSeconds(party.LastUpdated); if (lastUpdated.AddSeconds(party.OidcDiscoveryUpdateRate.Value) >= DateTimeOffset.UtcNow) { return; } var db = redisConnectionMultiplexer.GetDatabase(); var key = UpdateWaitPeriodKey(party.Id); if (await db.KeyExistsAsync(key)) { logger.ScopeTrace($"Up party '{party.Id}' not updated because another update is in progress."); return; } else { await db.StringSetAsync(key, true, TimeSpan.FromSeconds(settings.UpPartyUpdateWaitPeriod)); } var failingUpdateCount = (long?)await db.StringGetAsync(FailingUpdateCountKey(party.Id)); if (failingUpdateCount.HasValue && failingUpdateCount.Value >= settings.UpPartyMaxFailingUpdate) { party.UpdateState = PartyUpdateStates.AutomaticStopped; await tenantRepository.SaveAsync(party); await db.KeyDeleteAsync(FailingUpdateCountKey(party.Id)); return; } try { try { (var oidcDiscovery, var jsonWebKeySet) = await oidcDiscoveryReadLogic.GetOidcDiscoveryAndValidateAsync(party.Authority); party.LastUpdated = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); party.Issuers = new List <string> { oidcDiscovery.Issuer }; party.Client.AuthorizeUrl = oidcDiscovery.AuthorizationEndpoint; party.Client.TokenUrl = oidcDiscovery.TokenEndpoint; if (!oidcDiscovery.EndSessionEndpoint.IsNullOrEmpty()) { party.Client.EndSessionUrl = oidcDiscovery.EndSessionEndpoint; } party.Keys = jsonWebKeySet.Keys?.ToList(); } catch (Exception ex) { throw new EndpointException("Failing OIDC discovery.", ex) { RouteBinding = RouteBinding }; } await tenantRepository.SaveAsync(party); logger.ScopeTrace($"Up party '{party.Id}' updated by OIDC discovery.", triggerEvent: true); await db.KeyDeleteAsync(FailingUpdateCountKey(party.Id)); } catch (Exception ex) { await db.StringIncrementAsync(FailingUpdateCountKey(party.Id)); logger.Warning(ex); } }
private OidcUpPartyViewModel ToViewModel(GeneralOidcUpPartyViewModel generalOidcUpParty, OidcUpParty oidcUpParty) { return(oidcUpParty.Map <OidcUpPartyViewModel>(afterMap => { if (oidcUpParty.UpdateState == PartyUpdateStates.Manual) { afterMap.IsManual = true; } if (oidcUpParty.UpdateState == PartyUpdateStates.AutomaticStopped) { afterMap.AutomaticStopped = true; } else { afterMap.AutomaticStopped = false; } afterMap.EnableSingleLogout = !oidcUpParty.DisableSingleLogout; if (oidcUpParty.Client != null) { afterMap.Client.EnableFrontChannelLogout = !oidcUpParty.Client.DisableFrontChannelLogout; } generalOidcUpParty.KeyInfoList.Clear(); foreach (var key in afterMap.Keys) { if (key.Kty == MTokens.JsonWebAlgorithmsKeyTypes.RSA && key.X5c?.Count >= 1) { generalOidcUpParty.KeyInfoList.Add(new KeyInfoViewModel { Subject = key.CertificateInfo.Subject, ValidFrom = key.CertificateInfo.ValidFrom, ValidTo = key.CertificateInfo.ValidTo, IsValid = key.CertificateInfo.IsValid(), Thumbprint = key.CertificateInfo.Thumbprint, KeyId = key.Kid, Key = key }); } else { generalOidcUpParty.KeyInfoList.Add(new KeyInfoViewModel { KeyId = key.Kid, Key = key }); } } if (afterMap.ClaimTransforms?.Count > 0) { afterMap.ClaimTransforms = afterMap.ClaimTransforms.MapClaimTransforms(); } })); }
private async Task <(List <Claim>, string)> HandleAuthorizationCodeResponseAsync(OidcUpParty party, OidcUpSequenceData sequenceData, string code) { var tokenResponse = await TokenRequestAsync(party.Client, code, sequenceData); return(await ValidateTokensAsync(party, sequenceData, tokenResponse.IdToken, tokenResponse.AccessToken, false)); }
public async Task CreateOidcUpPartyAsync(OidcUpParty party) => await PostAsync(oidcApiUri, party);
public async Task UpdateOidcUpPartyAsync(OidcUpParty party) => await PutAsync(oidcApiUri, party);
public async Task <OidcUpParty> UpdateOidcUpPartyAsync(OidcUpParty party) => await PutResponseAsync <OidcUpParty, OidcUpParty>(oidcApiUri, party);