public async Task <IEndpointResult> ProcessAsync(HttpContext context) { _logger.LogTrace("Processing discovery request."); // validate HTTP if (!HttpMethods.IsGet(context.Request.Method)) { _logger.LogWarning("Discovery endpoint only supports GET requests"); return(new StatusCodeResult(HttpStatusCode.MethodNotAllowed)); } _logger.LogDebug("Start discovery request"); if (!_options.Endpoints.EnableDiscoveryEndpoint) { _logger.LogInformation("Discovery endpoint disabled. 404."); return(new StatusCodeResult(HttpStatusCode.NotFound)); } var baseUrl = context.GetIdentityServerBaseUrl().EnsureTrailingSlash(); var issuerUri = await _issuerNameService.GetCurrentAsync(); // generate response _logger.LogTrace("Calling into discovery response generator: {type}", _responseGenerator.GetType().FullName); var response = await _responseGenerator.CreateDiscoveryDocumentAsync(baseUrl, issuerUri); return(new DiscoveryDocumentResult(response, _options.Discovery.ResponseCacheInterval)); }
/// <summary> /// Invokes the middleware. /// </summary> /// <param name="context">The context.</param> /// <param name="router">The router.</param> /// <param name="session">The user session.</param> /// <param name="events">The event service.</param> /// <param name="issuerNameService">The issuer name service</param> /// <param name="backChannelLogoutService"></param> /// <returns></returns> public async Task Invoke( HttpContext context, IEndpointRouter router, IUserSession session, IEventService events, IIssuerNameService issuerNameService, IBackChannelLogoutService backChannelLogoutService) { // this will check the authentication session and from it emit the check session // cookie needed from JS-based signout clients. await session.EnsureSessionIdCookieAsync(); context.Response.OnStarting(async() => { if (context.GetSignOutCalled()) { _logger.LogDebug("SignOutCalled set; processing post-signout session cleanup."); // this clears our session id cookie so JS clients can detect the user has signed out await session.RemoveSessionIdCookieAsync(); // back channel logout var logoutContext = await session.GetLogoutNotificationContext(); if (logoutContext != null) { await backChannelLogoutService.SendLogoutNotificationsAsync(logoutContext); } } }); try { var endpoint = router.Find(context); if (endpoint != null) { LicenseValidator.ValidateIssuer(await issuerNameService.GetCurrentAsync()); _logger.LogInformation("Invoking IdentityServer endpoint: {endpointType} for {url}", endpoint.GetType().FullName, context.Request.Path.ToString()); var result = await endpoint.ProcessAsync(context); if (result != null) { _logger.LogTrace("Invoking result: {type}", result.GetType().FullName); await result.ExecuteAsync(context); } return; } } catch (Exception ex) { await events.RaiseAsync(new UnhandledExceptionEvent(ex)); _logger.LogCritical(ex, "Unhandled exception: {exception}", ex.Message); throw; } await _next(context); }
/// <inheritdoc/> public async Task SendLoginRequestAsync(BackchannelUserLoginRequest request) { var url = await _issuerNameService.GetCurrentAsync(); url += "/ciba?id=" + request.InternalId; _logger.LogWarning("IBackchannelAuthenticationUserNotificationService not implemented. But for testing, visit {url} to simulate what a user might need to do to complete the request.", url); }
/// <inheritdoc /> public async Task <string> StoreAsync(AuthenticationTicket ticket) { ArgumentNullException.ThrowIfNull(ticket); ticket.SetIssuer(await _issuerNameService.GetCurrentAsync()); var key = CryptoRandom.CreateUniqueId(format: CryptoRandom.OutputFormat.Hex); _logger.LogDebug("Creating entry in store for AuthenticationTicket, key {key}, with expiration: {expiration}", key, ticket.GetExpiration()); var session = new ServerSideSession { Key = key, Scheme = ticket.AuthenticationScheme, Created = ticket.GetIssued(), Renewed = ticket.GetIssued(), Expires = ticket.GetExpiration(), SubjectId = ticket.GetSubjectId(), SessionId = ticket.GetSessionId(), DisplayName = ticket.GetDisplayName(_options.ServerSideSessions.UserDisplayNameClaimType), Ticket = ticket.Serialize(_protector) }; await _store.CreateSessionAsync(session); return(key); }
private async Task <SecurityToken> CreateSecurityTokenAsync(SignInValidationResult result, ClaimsIdentity outgoingSubject) { var credentials = await _keys.GetSigningCredentialsAsync().ConfigureAwait(false); var x509Key = new X509SecurityKey(credentials.Key.GetX509Certificate(_keys)); var relyingParty = result.RelyingParty; var descriptor = new SecurityTokenDescriptor { Audience = result.Client.ClientId, IssuedAt = DateTime.UtcNow, NotBefore = DateTime.UtcNow, Expires = DateTime.UtcNow.AddSeconds(result.Client.IdentityTokenLifetime), SigningCredentials = new SigningCredentials(x509Key, relyingParty.SignatureAlgorithm, relyingParty.DigestAlgorithm), Subject = outgoingSubject, #if DUENDE Issuer = await _issuerNameService.GetCurrentAsync().ConfigureAwait(false), #else Issuer = _contextAccessor.HttpContext.GetIdentityServerIssuerUri(), #endif }; if (result.RelyingParty.EncryptionCertificate != null) { descriptor.EncryptingCredentials = new X509EncryptingCredentials(result.RelyingParty.EncryptionCertificate); } var handler = CreateTokenHandler(result.RelyingParty.TokenType); return(handler.CreateToken(descriptor)); }
/// <summary> /// Create a personal access token for the current user and client /// </summary> /// <param name="context">The Http context</param> /// <param name="isRefenceToken">Creeate a reference token if true otherwise a JWT token</param> /// <param name="lifetimeDays">The token life time</param> /// <param name="apis">The list of audience</param> /// <param name="scopes">The list of scopes</param> /// <param name="claimTypes">The list of claims types</param> /// <returns></returns> /// <exception cref="System.ArgumentNullException">apis</exception> /// <exception cref="System.InvalidOperationException">Client id not found in user claims. /// or /// Client not found for client id '{clientId}'. /// or /// Api '{api}' not found in '{client.ClientName}' allowed scopes. /// or /// Scope '{scope}' not found in '{client.ClientName}' allowed scopes.</exception> public async Task <string> CreatePersonalAccessTokenAsync(HttpContext context, bool isRefenceToken, int lifetimeDays, IEnumerable <string> apis, IEnumerable <string> scopes, IEnumerable <string> claimTypes) { CheckParams(apis); scopes ??= Array.Empty <string>(); claimTypes ??= Array.Empty <string>(); claimTypes = claimTypes.Where(c => c != JwtClaimTypes.Name && c != JwtClaimTypes.ClientId && c != JwtClaimTypes.Subject); var user = new ClaimsPrincipal(new ClaimsIdentity(context.User.Claims.Select(c => { if (JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.TryGetValue(c.Type, out string newType)) { return(new Claim(newType, c.Value)); } return(c); }), "Bearer", JwtClaimTypes.Name, JwtClaimTypes.Role)); var clientId = user.Claims.First(c => c.Type == JwtClaimTypes.ClientId).Value; await ValidateRequestAsync(apis, scopes, user, clientId).ConfigureAwait(false); #if DUENDE var issuer = await _issuerNameService.GetCurrentAsync().ConfigureAwait(false); #else var issuer = context.GetIdentityServerIssuerUri(); #endif var sub = user.FindFirstValue(JwtClaimTypes.Subject) ?? user.FindFirstValue("nameid"); var userName = user.Identity.Name; return(await _tokenService.CreateSecurityTokenAsync(new Token(IdentityServerConstants.TokenTypes.AccessToken) { AccessTokenType = isRefenceToken ? AccessTokenType.Reference : AccessTokenType.Jwt, Audiences = apis.ToArray(), ClientId = clientId, Claims = user.Claims.Where(c => claimTypes.Any(t => c.Type == t)) .Concat(new[] { new Claim(JwtClaimTypes.Name, userName), new Claim(JwtClaimTypes.ClientId, clientId), new Claim(JwtClaimTypes.Subject, sub) }) .Concat(scopes.Select(s => new Claim("scope", s))) .ToArray(), CreationTime = DateTime.UtcNow, Lifetime = Convert.ToInt32(TimeSpan.FromDays(lifetimeDays).TotalSeconds), Issuer = issuer })); }
private async Task <string> CreateMessage(BackchannelUserLoginRequest request) { var client = request.Client; var builder = new StringBuilder(); if (client.LogoUri is not null) { builder.Append("<div><img src=\""); builder.Append(client.LogoUri); builder.Append("\"></div>"); } builder.Append(_localizer["{0} is requesting your permission", client.ClientName]); if (!string.IsNullOrEmpty(request.BindingMessage)) { builder.Append("<div>"); builder.Append(_localizer["Verify that this identifier matches what the client is displaying: {0}", request.BindingMessage]); builder.Append("</div>"); } builder.Append("<div>"); builder.Append(_localizer[" Do you wish to continue?", client.ClientName]); builder.Append("</div>"); var issuer = await _nameService.GetCurrentAsync().ConfigureAwait(false); if (!issuer.EndsWith('/')) { issuer += "/"; } builder.Append("<div>"); builder.Append("<a href =\""); builder.Append(issuer); builder.Append("ciba/consent?id="); builder.Append(request.InternalId); builder.Append("\">"); builder.Append(_localizer["Yes, Continue"]); builder.Append("</a>"); builder.Append("</div>"); return(builder.ToString()); }
/// <summary> /// Issues a JWT. /// </summary> /// <param name="lifetime">The lifetime.</param> /// <param name="claims">The claims.</param> /// <returns></returns> /// <exception cref="System.ArgumentNullException">claims</exception> public virtual async Task <string> IssueJwtAsync(int lifetime, IEnumerable <Claim> claims) { if (claims == null) { throw new ArgumentNullException(nameof(claims)); } var issuer = await IssuerNameService.GetCurrentAsync(); var token = new Token { CreationTime = _clock.UtcNow.UtcDateTime, Issuer = issuer, Lifetime = lifetime, Claims = new HashSet <Claim>(claims, new ClaimComparer()) }; return(await _tokenCreation.CreateTokenAsync(token)); }
/// <summary> /// Generates the asynchronous. /// </summary> /// <param name="wsfedEndpoint">The wsfed endpoint.</param> /// <returns></returns> public async Task <WsFederationConfiguration> GenerateAsync(string wsfedEndpoint) { var credentials = await _keys.GetSigningCredentialsAsync().ConfigureAwait(false); var key = credentials.Key; var keyInfo = new KeyInfo(key.GetX509Certificate(_keys)); #if DUENDE var issuer = await _issuerNameService.GetCurrentAsync().ConfigureAwait(false); #else var issuer = _contextAccessor.HttpContext.GetIdentityServerIssuerUri(); #endif var signingCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.Sha256Digest); var config = new WsFederationConfiguration() { Issuer = issuer, TokenEndpoint = wsfedEndpoint, SigningCredentials = signingCredentials, }; config.SigningKeys.Add(key); config.KeyInfos.Add(keyInfo); return(config); }
internal async Task <KeyContainer> CreateAndStoreNewKeyAsync(SigningAlgorithmOptions alg) { _logger.LogDebug("Creating new key."); var now = _clock.UtcNow.UtcDateTime; var iss = await _issuerNameService.GetCurrentAsync(); KeyContainer container = null; if (alg.IsRsaKey) { var rsa = CryptoHelper.CreateRsaSecurityKey(_options.RsaKeySize); container = alg.UseX509Certificate ? new X509KeyContainer(rsa, alg.Name, now, _options.KeyRetirementAge, iss) : (KeyContainer) new RsaKeyContainer(rsa, alg.Name, now); } else if (alg.IsEcKey) { var ec = CryptoHelper.CreateECDsaSecurityKey(CryptoHelper.GetCurveNameFromSigningAlgorithm(alg.Name)); // X509 certs don't currently work with EC keys. container = //_options.WrapKeysInX509Certificate ? //new X509KeyContainer(ec, alg, now, _options.KeyRetirementAge, iss) : (KeyContainer) new EcKeyContainer(ec, alg.Name, now); } else { throw new Exception($"Invalid alg '{alg}'"); } var key = _protector.Protect(container); await _store.StoreKeyAsync(key); _logger.LogInformation("Created and stored new key with kid {kid}.", container.Id); return(container); }
/// <summary> /// Validates a secret /// </summary> /// <param name="secrets">The stored secrets.</param> /// <param name="parsedSecret">The received secret.</param> /// <returns> /// A validation result /// </returns> /// <exception cref="System.ArgumentException">ParsedSecret.Credential is not a JWT token</exception> public async Task <SecretValidationResult> ValidateAsync(IEnumerable <Secret> secrets, ParsedSecret parsedSecret) { var fail = new SecretValidationResult { Success = false }; var success = new SecretValidationResult { Success = true }; if (parsedSecret.Type != IdentityServerConstants.ParsedSecretTypes.JwtBearer) { return(fail); } if (!(parsedSecret.Credential is string jwtTokenString)) { _logger.LogError("ParsedSecret.Credential is not a string."); return(fail); } List <SecurityKey> trustedKeys; try { trustedKeys = await secrets.GetKeysAsync(); } catch (Exception e) { _logger.LogError(e, "Could not parse secrets"); return(fail); } if (!trustedKeys.Any()) { _logger.LogError("There are no keys available to validate client assertion."); return(fail); } var validAudiences = new[] { // token endpoint URL string.Concat((await _issuerNameService.GetCurrentAsync()).EnsureTrailingSlash(), Constants.ProtocolRoutePaths.Token) }; var tokenValidationParameters = new TokenValidationParameters { IssuerSigningKeys = trustedKeys, ValidateIssuerSigningKey = true, ValidIssuer = parsedSecret.Id, ValidateIssuer = true, ValidAudiences = validAudiences, ValidateAudience = true, RequireSignedTokens = true, RequireExpirationTime = true, ClockSkew = TimeSpan.FromMinutes(5) }; try { var handler = new JwtSecurityTokenHandler(); handler.ValidateToken(jwtTokenString, tokenValidationParameters, out var token); var jwtToken = (JwtSecurityToken)token; if (jwtToken.Subject != jwtToken.Issuer) { _logger.LogError("Both 'sub' and 'iss' in the client assertion token must have a value of client_id."); return(fail); } var exp = jwtToken.Payload.Exp; if (!exp.HasValue) { _logger.LogError("exp is missing."); return(fail); } var jti = jwtToken.Payload.Jti; if (jti.IsMissing()) { _logger.LogError("jti is missing."); return(fail); } if (await _replayCache.ExistsAsync(Purpose, jti)) { _logger.LogError("jti is found in replay cache. Possible replay attack."); return(fail); } else { await _replayCache.AddAsync(Purpose, jti, DateTimeOffset.FromUnixTimeSeconds(exp.Value).AddMinutes(5)); } return(success); } catch (Exception e) { _logger.LogError(e, "JWT token validation error"); return(fail); } }
/// <summary> /// Validates a secret /// </summary> /// <param name="secrets">The stored secrets.</param> /// <param name="parsedSecret">The received secret.</param> /// <returns> /// A validation result /// </returns> /// <exception cref="System.ArgumentException">ParsedSecret.Credential is not a JWT token</exception> public async Task <SecretValidationResult> ValidateAsync(IEnumerable <Secret> secrets, ParsedSecret parsedSecret) { var fail = new SecretValidationResult { Success = false }; var success = new SecretValidationResult { Success = true }; if (parsedSecret.Type != IdentityServerConstants.ParsedSecretTypes.JwtBearer) { return(fail); } if (!(parsedSecret.Credential is string jwtTokenString)) { _logger.LogError("ParsedSecret.Credential is not a string."); return(fail); } List <SecurityKey> trustedKeys; try { trustedKeys = await secrets.GetKeysAsync(); } catch (Exception e) { _logger.LogError(e, "Could not parse secrets"); return(fail); } if (!trustedKeys.Any()) { _logger.LogError("There are no keys available to validate client assertion."); return(fail); } var validAudiences = new[] { // token endpoint URL string.Concat(_urls.BaseUrl.EnsureTrailingSlash(), Constants.ProtocolRoutePaths.Token), // TODO: remove the issuer URL in a future major release? // issuer URL string.Concat((await _issuerNameService.GetCurrentAsync()).EnsureTrailingSlash(), Constants.ProtocolRoutePaths.Token) }.Distinct(); var tokenValidationParameters = new TokenValidationParameters { IssuerSigningKeys = trustedKeys, ValidateIssuerSigningKey = true, ValidIssuer = parsedSecret.Id, ValidateIssuer = true, ValidAudiences = validAudiences, ValidateAudience = true, RequireSignedTokens = true, RequireExpirationTime = true, ClockSkew = TimeSpan.FromMinutes(5) }; var handler = new JsonWebTokenHandler() { MaximumTokenSizeInBytes = _options.InputLengthRestrictions.Jwt }; var result = handler.ValidateToken(jwtTokenString, tokenValidationParameters); if (!result.IsValid) { _logger.LogError(result.Exception, "JWT token validation error"); return(fail); } var jwtToken = (JsonWebToken)result.SecurityToken; if (jwtToken.Subject != jwtToken.Issuer) { _logger.LogError("Both 'sub' and 'iss' in the client assertion token must have a value of client_id."); return(fail); } var exp = jwtToken.ValidTo; if (exp == DateTime.MinValue) { _logger.LogError("exp is missing."); return(fail); } var jti = jwtToken.Id; if (jti.IsMissing()) { _logger.LogError("jti is missing."); return(fail); } if (await _replayCache.ExistsAsync(Purpose, jti)) { _logger.LogError("jti is found in replay cache. Possible replay attack."); return(fail); } else { await _replayCache.AddAsync(Purpose, jti, exp.AddMinutes(5)); } return(success); }
public async Task <AuthorizeRequestValidationResult> ValidateAsync(NameValueCollection parameters, ClaimsPrincipal subject = null) { _logger.LogDebug("Start authorize request protocol validation"); var request = new ValidatedAuthorizeRequest { Options = _options, IssuerName = await _issuerNameService.GetCurrentAsync(), Subject = subject ?? Principal.Anonymous, Raw = parameters ?? throw new ArgumentNullException(nameof(parameters)) }; // load client_id // client_id must always be present on the request var loadClientResult = await LoadClientAsync(request); if (loadClientResult.IsError) { return(loadClientResult); } // load request object var roLoadResult = await LoadRequestObjectAsync(request); if (roLoadResult.IsError) { return(roLoadResult); } // validate request object var roValidationResult = await ValidateRequestObjectAsync(request); if (roValidationResult.IsError) { return(roValidationResult); } // validate client_id and redirect_uri var clientResult = await ValidateClientAsync(request); if (clientResult.IsError) { return(clientResult); } // state, response_type, response_mode var mandatoryResult = ValidateCoreParameters(request); if (mandatoryResult.IsError) { return(mandatoryResult); } // scope, scope restrictions and plausability, and resource indicators var scopeResult = await ValidateScopeAndResourceAsync(request); if (scopeResult.IsError) { return(scopeResult); } // nonce, prompt, acr_values, login_hint etc. var optionalResult = await ValidateOptionalParametersAsync(request); if (optionalResult.IsError) { return(optionalResult); } // custom validator _logger.LogDebug("Calling into custom validator: {type}", _customValidator.GetType().FullName); var context = new CustomAuthorizeRequestValidationContext { Result = new AuthorizeRequestValidationResult(request) }; await _customValidator.ValidateAsync(context); var customResult = context.Result; if (customResult.IsError) { LogError("Error in custom validation", customResult.Error, request); return(Invalid(request, customResult.Error, customResult.ErrorDescription)); } _logger.LogTrace("Authorize request protocol validation successful"); LicenseValidator.ValidateClient(request.ClientId); return(Valid(request)); }
private async Task <TokenValidationResult> ValidateJwtAsync(string jwtString, IEnumerable <SecurityKeyInfo> validationKeys, bool validateLifetime = true, string audience = null) { using var activity = Tracing.BasicActivitySource.StartActivity("TokenValidator.ValidateJwt"); var handler = new JsonWebTokenHandler(); var parameters = new TokenValidationParameters { ValidIssuer = await _issuerNameService.GetCurrentAsync(), IssuerSigningKeys = validationKeys.Select(k => k.Key), ValidateLifetime = validateLifetime }; if (audience.IsPresent()) { parameters.ValidAudience = audience; } else { parameters.ValidateAudience = false; // if no audience is specified, we make at least sure that it is an access token if (_options.AccessTokenJwtType.IsPresent()) { parameters.ValidTypes = new[] { _options.AccessTokenJwtType }; } } var result = handler.ValidateToken(jwtString, parameters); if (!result.IsValid) { if (result.Exception is SecurityTokenExpiredException expiredException) { _logger.LogInformation(expiredException, "JWT token validation error: {exception}", expiredException.Message); return(Invalid(OidcConstants.ProtectedResourceErrors.ExpiredToken)); } else { _logger.LogError(result.Exception, "JWT token validation error: {exception}", result.Exception.Message); return(Invalid(OidcConstants.ProtectedResourceErrors.InvalidToken)); } } var id = result.ClaimsIdentity; // if access token contains an ID, log it var jwtId = id.FindFirst(JwtClaimTypes.JwtId); if (jwtId != null) { _log.JwtId = jwtId.Value; } // load the client that belongs to the client_id claim Client client = null; var clientId = id.FindFirst(JwtClaimTypes.ClientId); if (clientId != null) { client = await _clients.FindEnabledClientByIdAsync(clientId.Value); if (client == null) { LogError($"Client deleted or disabled: {clientId}"); return(Invalid(OidcConstants.ProtectedResourceErrors.InvalidToken)); } } var claims = id.Claims.ToList(); // check the scope format (array vs space delimited string) var scopes = claims.Where(c => c.Type == JwtClaimTypes.Scope).ToArray(); if (scopes.Any()) { foreach (var scope in scopes) { if (scope.Value.Contains(" ")) { claims.Remove(scope); var values = scope.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries); foreach (var value in values) { claims.Add(new Claim(JwtClaimTypes.Scope, value)); } } } } return(new TokenValidationResult { IsError = false, Claims = claims, Client = client, Jwt = jwtString }); }
/// <summary> /// Invokes the middleware. /// </summary> /// <param name="context">The context.</param> /// <param name="router">The router.</param> /// <param name="userSession">The user session.</param> /// <param name="events">The event service.</param> /// <param name="issuerNameService">The issuer name service</param> /// <param name="sessionCoordinationService"></param> /// <returns></returns> public async Task Invoke( HttpContext context, IEndpointRouter router, IUserSession userSession, IEventService events, IIssuerNameService issuerNameService, ISessionCoordinationService sessionCoordinationService) { // this will check the authentication session and from it emit the check session // cookie needed from JS-based signout clients. await userSession.EnsureSessionIdCookieAsync(); context.Response.OnStarting(async() => { if (context.GetSignOutCalled()) { _logger.LogDebug("SignOutCalled set; processing post-signout session cleanup."); // this clears our session id cookie so JS clients can detect the user has signed out await userSession.RemoveSessionIdCookieAsync(); var user = await userSession.GetUserAsync(); if (user != null) { var session = new UserSession { SubjectId = user.GetSubjectId(), SessionId = await userSession.GetSessionIdAsync(), DisplayName = user.GetDisplayName(), ClientIds = (await userSession.GetClientListAsync()).ToList(), Issuer = await issuerNameService.GetCurrentAsync() }; await sessionCoordinationService.ProcessLogoutAsync(session); } } }); try { var endpoint = router.Find(context); if (endpoint != null) { var endpointType = endpoint.GetType().FullName; using var activity = Tracing.BasicActivitySource.StartActivity("IdentityServerProtocolRequest"); activity?.SetTag(Tracing.Properties.EndpointType, endpointType); LicenseValidator.ValidateIssuer(await issuerNameService.GetCurrentAsync()); _logger.LogInformation("Invoking IdentityServer endpoint: {endpointType} for {url}", endpointType, context.Request.Path.ToString()); var result = await endpoint.ProcessAsync(context); if (result != null) { _logger.LogTrace("Invoking result: {type}", result.GetType().FullName); await result.ExecuteAsync(context); } return; } } catch (Exception ex) { await events.RaiseAsync(new UnhandledExceptionEvent(ex)); _logger.LogCritical(ex, "Unhandled exception: {exception}", ex.Message); throw; } await _next(context); }
private async Task <TokenValidationResult> ValidateJwtAsync(string jwt, IEnumerable <SecurityKeyInfo> validationKeys, bool validateLifetime = true, string audience = null) { var handler = new JwtSecurityTokenHandler(); handler.InboundClaimTypeMap.Clear(); var parameters = new TokenValidationParameters { ValidIssuer = await _issuerNameService.GetCurrentAsync(), IssuerSigningKeys = validationKeys.Select(k => k.Key), ValidateLifetime = validateLifetime }; if (audience.IsPresent()) { parameters.ValidAudience = audience; } else { parameters.ValidateAudience = false; } try { var id = handler.ValidateToken(jwt, parameters, out var securityToken); var jwtSecurityToken = securityToken as JwtSecurityToken; // if no audience is specified, we make at least sure that it is an access token if (audience.IsMissing()) { if (_options.AccessTokenJwtType.IsPresent()) { var type = jwtSecurityToken.Header.Typ; if (!string.Equals(type, _options.AccessTokenJwtType)) { return(new TokenValidationResult { IsError = true, Error = "invalid JWT token type" }); } } } // if access token contains an ID, log it var jwtId = id.FindFirst(JwtClaimTypes.JwtId); if (jwtId != null) { _log.JwtId = jwtId.Value; } // load the client that belongs to the client_id claim Client client = null; var clientId = id.FindFirst(JwtClaimTypes.ClientId); if (clientId != null) { client = await _clients.FindEnabledClientByIdAsync(clientId.Value); if (client == null) { throw new InvalidOperationException("Client does not exist anymore."); } } var claims = id.Claims.ToList(); // check the scope format (array vs space delimited string) var scopes = claims.Where(c => c.Type == JwtClaimTypes.Scope).ToArray(); if (scopes.Any()) { foreach (var scope in scopes) { if (scope.Value.Contains(" ")) { claims.Remove(scope); var values = scope.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries); foreach (var value in values) { claims.Add(new Claim(JwtClaimTypes.Scope, value)); } } } } return(new TokenValidationResult { IsError = false, Claims = claims, Client = client, Jwt = jwt }); } catch (SecurityTokenExpiredException expiredException) { _logger.LogInformation(expiredException, "JWT token validation error: {exception}", expiredException.Message); return(Invalid(OidcConstants.ProtectedResourceErrors.ExpiredToken)); } catch (Exception ex) { _logger.LogError(ex, "JWT token validation error: {exception}", ex.Message); return(Invalid(OidcConstants.ProtectedResourceErrors.InvalidToken)); } }
/// <summary> /// Validates the request. /// </summary> /// <param name="parameters">The parameters.</param> /// <param name="clientValidationResult">The client validation result.</param> /// <returns></returns> /// <exception cref="System.ArgumentNullException"> /// parameters /// or /// client /// </exception> public async Task <TokenRequestValidationResult> ValidateRequestAsync(NameValueCollection parameters, ClientSecretValidationResult clientValidationResult) { using var activity = Tracing.BasicActivitySource.StartActivity("TokenRequestValidator.ValidateRequest"); _logger.LogDebug("Start token request validation"); _validatedRequest = new ValidatedTokenRequest { IssuerName = await _issuerNameService.GetCurrentAsync(), Raw = parameters ?? throw new ArgumentNullException(nameof(parameters)), Options = _options }; if (clientValidationResult == null) { throw new ArgumentNullException(nameof(clientValidationResult)); } _validatedRequest.SetClient(clientValidationResult.Client, clientValidationResult.Secret, clientValidationResult.Confirmation); ///////////////////////////////////////////// // check client protocol type ///////////////////////////////////////////// if (_validatedRequest.Client.ProtocolType != IdentityServerConstants.ProtocolTypes.OpenIdConnect) { LogError("Invalid protocol type for client", new { clientId = _validatedRequest.Client.ClientId, expectedProtocolType = IdentityServerConstants.ProtocolTypes.OpenIdConnect, actualProtocolType = _validatedRequest.Client.ProtocolType }); return(Invalid(OidcConstants.TokenErrors.InvalidClient)); } ///////////////////////////////////////////// // check grant type ///////////////////////////////////////////// var grantType = parameters.Get(OidcConstants.TokenRequest.GrantType); if (grantType.IsMissing()) { LogError("Grant type is missing"); return(Invalid(OidcConstants.TokenErrors.UnsupportedGrantType)); } if (grantType.Length > _options.InputLengthRestrictions.GrantType) { LogError("Grant type is too long"); return(Invalid(OidcConstants.TokenErrors.UnsupportedGrantType)); } _validatedRequest.GrantType = grantType; ////////////////////////////////////////////////////////// // check for resource indicator and basic formatting ////////////////////////////////////////////////////////// var resourceIndicators = parameters.GetValues(OidcConstants.TokenRequest.Resource) ?? Enumerable.Empty <string>(); if (resourceIndicators?.Any(x => x.Length > _options.InputLengthRestrictions.ResourceIndicatorMaxLength) == true) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Resource indicator maximum length exceeded")); } if (!resourceIndicators.AreValidResourceIndicatorFormat(_logger)) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Invalid resource indicator format")); } if (resourceIndicators.Count() > 1) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Multiple resource indicators not supported on token endpoint.")); } _validatedRequest.RequestedResourceIndicator = resourceIndicators.SingleOrDefault(); ////////////////////////////////////////////////////////// // run specific logic for grants ////////////////////////////////////////////////////////// switch (grantType) { case OidcConstants.GrantTypes.AuthorizationCode: return(await RunValidationAsync(ValidateAuthorizationCodeRequestAsync, parameters)); case OidcConstants.GrantTypes.ClientCredentials: return(await RunValidationAsync(ValidateClientCredentialsRequestAsync, parameters)); case OidcConstants.GrantTypes.Password: return(await RunValidationAsync(ValidateResourceOwnerCredentialRequestAsync, parameters)); case OidcConstants.GrantTypes.RefreshToken: return(await RunValidationAsync(ValidateRefreshTokenRequestAsync, parameters)); case OidcConstants.GrantTypes.DeviceCode: return(await RunValidationAsync(ValidateDeviceCodeRequestAsync, parameters)); case OidcConstants.GrantTypes.Ciba: return(await RunValidationAsync(ValidateCibaRequestRequestAsync, parameters)); default: return(await RunValidationAsync(ValidateExtensionGrantRequestAsync, parameters)); } }
/// <inheritdoc/> public async Task <IEnumerable <string> > GetFrontChannelLogoutNotificationsUrlsAsync(LogoutNotificationContext context) { var frontChannelUrls = new List <string>(); foreach (var clientId in context.ClientIds) { var client = await _clientStore.FindEnabledClientByIdAsync(clientId); if (client != null) { if (client.FrontChannelLogoutUri.IsPresent()) { var url = client.FrontChannelLogoutUri; // add session id if required if (client.ProtocolType == IdentityServerConstants.ProtocolTypes.OpenIdConnect) { if (client.FrontChannelLogoutSessionRequired) { url = url.AddQueryString(OidcConstants.EndSessionRequest.Sid, context.SessionId); url = url.AddQueryString(OidcConstants.EndSessionRequest.Issuer, await _issuerNameService.GetCurrentAsync()); } } else if (client.ProtocolType == IdentityServerConstants.ProtocolTypes.WsFederation) { url = url.AddQueryString(Constants.WsFedSignOut.LogoutUriParameterName, Constants.WsFedSignOut.LogoutUriParameterValue); } frontChannelUrls.Add(url); } } } if (frontChannelUrls.Any()) { var msg = frontChannelUrls.Aggregate((x, y) => x + ", " + y); _logger.LogDebug("Client front-channel logout URLs: {0}", msg); } else { _logger.LogDebug("No client front-channel logout URLs"); } return(frontChannelUrls); }