private async Task CreateAuthorizationAsync( [NotNull] AuthenticationTicket ticket, [NotNull] OpenIddictOptions options, [NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request) { var descriptor = new OpenIddictAuthorizationDescriptor { Principal = ticket.Principal, Status = OpenIddictConstants.Statuses.Valid, Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject), Type = OpenIddictConstants.AuthorizationTypes.AdHoc }; foreach (var property in ticket.Properties.Items) { descriptor.Properties.Add(property); } foreach (var scope in ticket.GetScopes()) { descriptor.Scopes.Add(scope); } // If the client application is known, bind it to the authorization. if (!string.IsNullOrEmpty(request.ClientId)) { var application = await Applications.FindByClientIdAsync(request.ClientId, context.RequestAborted); if (application == null) { throw new InvalidOperationException("The client application cannot be retrieved from the database."); } descriptor.ApplicationId = await Applications.GetIdAsync(application, context.RequestAborted); } var authorization = await Authorizations.CreateAsync(descriptor, context.RequestAborted); if (authorization != null) { var identifier = await Authorizations.GetIdAsync(authorization, context.RequestAborted); if (string.IsNullOrEmpty(request.ClientId)) { Logger.LogInformation("An ad hoc authorization was automatically created and " + "associated with an unknown application: {Identifier}.", identifier); } else { Logger.LogInformation("An ad hoc authorization was automatically created and " + "associated with the '{ClientId}' application: {Identifier}.", request.ClientId, identifier); } // Attach the unique identifier of the ad hoc authorization to the authentication ticket // so that it is attached to all the derived tokens, allowing batched revocations support. ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, identifier); } }
public override async Task ValidateIntrospectionRequest([NotNull] ValidateIntrospectionRequestContext context) { // Note: the OpenID Connect server middleware supports unauthenticated introspection requests // but OpenIddict uses a stricter policy preventing unauthenticated/public applications // from using the introspection endpoint, as required by the specifications. // See https://tools.ietf.org/html/rfc7662#section-2.1 for more information. if (string.IsNullOrEmpty(context.ClientId) || string.IsNullOrEmpty(context.ClientSecret)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "Clients must be authenticated to use the introspection endpoint."); return; } // Retrieve the application details corresponding to the requested client_id. var application = await Applications.FindByClientIdAsync(context.ClientId, context.HttpContext.RequestAborted); if (application == null) { Logger.LogError("The introspection request was rejected because the client " + "application was not found: '{ClientId}'.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Application not found in the database: ensure that your client_id is correct."); return; } // Reject non-confidential applications. if (!await Applications.IsConfidentialAsync(application, context.HttpContext.RequestAborted)) { Logger.LogError("The introspection request was rejected because the public application " + "'{ClientId}' was not allowed to use this endpoint.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Public applications are not allowed to use the introspection endpoint."); return; } // Validate the client credentials. if (!await Applications.ValidateClientSecretAsync(application, context.ClientSecret, context.HttpContext.RequestAborted)) { Logger.LogError("The introspection request was rejected because the confidential application " + "'{ClientId}' didn't specify valid client credentials.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Invalid credentials: ensure that you specified a correct client_secret."); return; } context.Validate(); }
public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) { var options = (OpenIddictOptions)context.Options; if (!options.DisableTokenRevocation) { // Resolve the subject from the authentication ticket. If it cannot be found, throw an exception. var subject = context.Ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject); if (string.IsNullOrEmpty(subject)) { throw new InvalidOperationException("The subject associated with the authentication ticket cannot be retrieved."); } // If a null value was returned by CreateAsync, return immediately. var token = await Tokens.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, subject, context.HttpContext.RequestAborted); if (token == null) { return; } // Throw an exception if the token identifier can't be resolved. var identifier = await Tokens.GetIdAsync(token, context.HttpContext.RequestAborted); if (string.IsNullOrEmpty(identifier)) { throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty."); } // Attach the key returned by the underlying store // to the refresh token to override the default GUID // generated by the OpenID Connect server middleware. context.Ticket.SetProperty(OpenIdConnectConstants.Properties.TokenId, identifier); // If the client application is known, associate it with the token. if (!string.IsNullOrEmpty(context.Request.ClientId)) { var application = await Applications.FindByClientIdAsync(context.Request.ClientId, context.HttpContext.RequestAborted); if (application == null) { throw new InvalidOperationException("The client application cannot be retrieved from the database."); } await Tokens.SetClientAsync(token, await Applications.GetIdAsync(application, context.HttpContext.RequestAborted), context.HttpContext.RequestAborted); } // If an authorization identifier was specified, bind it to the token. var authorization = context.Ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId); if (!string.IsNullOrEmpty(authorization)) { await Tokens.SetAuthorizationAsync(token, authorization, context.HttpContext.RequestAborted); } } }
public override async Task ValidateIntrospectionRequest([NotNull] ValidateIntrospectionRequestContext context) { // Note: the OpenID Connect server middleware supports unauthenticated introspection requests // but OpenIddict uses a stricter policy preventing unauthenticated/public applications // from using the introspection endpoint, as required by the specifications. // See https://tools.ietf.org/html/rfc7662#section-2.1 for more information. if (string.IsNullOrEmpty(context.ClientId) || string.IsNullOrEmpty(context.ClientSecret)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The mandatory 'client_id' and/or 'client_secret' parameters are missing."); return; } // Retrieve the application details corresponding to the requested client_id. var application = await Applications.FindByClientIdAsync(context.ClientId); if (application == null) { Logger.LogError("The introspection request was rejected because the client " + "application was not found: '{ClientId}'.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "The specified 'client_id' parameter is invalid."); return; } // Store the application entity as a request property to make it accessible // from the other provider methods without having to call the store twice. context.Request.SetProperty($"{OpenIddictConstants.Properties.Application}:{context.ClientId}", application); // Reject the request if the application is not allowed to use the introspection endpoint. if (!await Applications.HasPermissionAsync(application, OpenIddictConstants.Permissions.Endpoints.Introspection)) { Logger.LogError("The introspection request was rejected because the application '{ClientId}' " + "was not allowed to use the introspection endpoint.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.UnauthorizedClient, description: "This client application is not allowed to use the introspection endpoint."); return; } // Reject introspection requests sent by public applications. if (await Applications.IsPublicAsync(application)) { Logger.LogError("The introspection request was rejected because the public application " + "'{ClientId}' was not allowed to use this endpoint.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "This client application is not allowed to use the introspection endpoint."); return; } // Validate the client credentials. if (!await Applications.ValidateClientSecretAsync(application, context.ClientSecret)) { Logger.LogError("The introspection request was rejected because the confidential or hybrid application " + "'{ClientId}' didn't specify valid client credentials.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "The specified client credentials are invalid."); return; } context.Validate(); }
private async Task <string> CreateTokenAsync( [NotNull] string type, [NotNull] AuthenticationTicket ticket, [NotNull] OpenIddictOptions options, [NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request, [NotNull] ISecureDataFormat <AuthenticationTicket> format) { Debug.Assert(!(options.DisableTokenRevocation && options.UseReferenceTokens), "Token revocation cannot be disabled when using reference tokens."); Debug.Assert(type == OpenIdConnectConstants.TokenUsages.AccessToken || type == OpenIdConnectConstants.TokenUsages.AuthorizationCode || type == OpenIdConnectConstants.TokenUsages.RefreshToken, "Only authorization codes, access and refresh tokens should be created using this method."); // When sliding expiration is disabled, the expiration date of generated refresh tokens is fixed // and must exactly match the expiration date of the refresh token used in the token request. if (request.IsTokenRequest() && request.IsRefreshTokenGrantType() && !options.UseSlidingExpiration && type == OpenIdConnectConstants.TokenUsages.RefreshToken) { var properties = request.GetProperty <AuthenticationTicket>( OpenIddictConstants.Properties.AuthenticationTicket)?.Properties; Debug.Assert(properties != null, "The authentication properties shouldn't be null."); ticket.Properties.ExpiresUtc = properties.ExpiresUtc; } if (options.DisableTokenRevocation) { return(null); } var descriptor = new OpenIddictTokenDescriptor { AuthorizationId = ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId), CreationDate = ticket.Properties.IssuedUtc, ExpirationDate = ticket.Properties.ExpiresUtc, Principal = ticket.Principal, Status = OpenIddictConstants.Statuses.Valid, Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject), Type = type }; foreach (var property in ticket.Properties.Items) { descriptor.Properties.Add(property); } string result = null; // When reference tokens are enabled or when the token is an authorization code or a // refresh token, remove the unnecessary properties from the authentication ticket. if (options.UseReferenceTokens || (type == OpenIdConnectConstants.TokenUsages.AuthorizationCode || type == OpenIdConnectConstants.TokenUsages.RefreshToken)) { ticket.Properties.IssuedUtc = ticket.Properties.ExpiresUtc = null; ticket.RemoveProperty(OpenIddictConstants.Properties.AuthorizationId) .RemoveProperty(OpenIdConnectConstants.Properties.TokenId); } // If reference tokens are enabled, create a new entry for // authorization codes, refresh tokens and access tokens. if (options.UseReferenceTokens) { // Note: the data format is automatically replaced at startup time to ensure // that encrypted tokens stored in the database cannot be considered as // valid tokens if the developer decides to disable reference tokens support. descriptor.Ciphertext = format.Protect(ticket); // Generate a new crypto-secure random identifier that will be // substituted to the ciphertext returned by the data format. var bytes = new byte[256 / 8]; options.RandomNumberGenerator.GetBytes(bytes); result = Base64UrlEncoder.Encode(bytes); // Compute the digest of the generated identifier and use // it as the hashed identifier of the reference token. // Doing that prevents token identifiers stolen from // the database from being used as valid reference tokens. using (var algorithm = SHA256.Create()) { descriptor.Hash = Convert.ToBase64String(algorithm.ComputeHash(bytes)); } } // Otherwise, only create a token metadata entry for authorization codes and refresh tokens. else if (type != OpenIdConnectConstants.TokenUsages.AuthorizationCode && type != OpenIdConnectConstants.TokenUsages.RefreshToken) { return(null); } // If the client application is known, associate it with the token. if (!string.IsNullOrEmpty(request.ClientId)) { var application = await Applications.FindByClientIdAsync(request.ClientId, context.RequestAborted); if (application == null) { throw new InvalidOperationException("The client application cannot be retrieved from the database."); } descriptor.ApplicationId = await Applications.GetIdAsync(application, context.RequestAborted); } // If a null value was returned by CreateAsync(), return immediately. var token = await Tokens.CreateAsync(descriptor, context.RequestAborted); if (token == null) { return(null); } // Throw an exception if the token identifier can't be resolved. var identifier = await Tokens.GetIdAsync(token, context.RequestAborted); if (string.IsNullOrEmpty(identifier)) { throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty."); } // Restore the token identifier using the unique // identifier attached with the database entry. ticket.SetTokenId(identifier); // Dynamically set the creation and expiration dates. ticket.Properties.IssuedUtc = descriptor.CreationDate; ticket.Properties.ExpiresUtc = descriptor.ExpirationDate; // Restore the authorization identifier using the identifier attached with the database entry. ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, descriptor.AuthorizationId); if (!string.IsNullOrEmpty(result)) { Logger.LogTrace("A new reference token was successfully generated and persisted " + "in the database: {Token} ; {Claims} ; {Properties}.", result, ticket.Principal.Claims, ticket.Properties.Items); } return(result); }
public override async Task ValidateRevocationRequest([NotNull] ValidateRevocationRequestContext context) { var options = (OpenIddictOptions)context.Options; Debug.Assert(!options.DisableTokenRevocation, "Token revocation support shouldn't be disabled at this stage."); // When token_type_hint is specified, reject the request if it doesn't correspond to a revocable token. if (!string.IsNullOrEmpty(context.Request.TokenTypeHint)) { if (string.Equals(context.Request.TokenTypeHint, OpenIdConnectConstants.TokenTypeHints.IdToken)) { context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedTokenType, description: "The specified 'token_type_hint' parameter is not supported."); return; } if (!options.UseReferenceTokens && string.Equals(context.Request.TokenTypeHint, OpenIdConnectConstants.TokenTypeHints.AccessToken)) { context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedTokenType, description: "The specified 'token_type_hint' parameter is not supported."); return; } } // Skip client authentication if the client identifier is missing or reject // the revocation request if client identification is set as required. // Note: the OpenID Connect server middleware will automatically ensure that // the calling application cannot revoke a refresh token if it's not // the intended audience, even if client authentication was skipped. if (string.IsNullOrEmpty(context.ClientId)) { // Reject the request if client identification is mandatory. if (options.RequireClientIdentification) { Logger.LogError("The revocation request was rejected becaused the " + "mandatory client_id parameter was missing or empty."); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The mandatory 'client_id' parameter is missing."); return; } Logger.LogDebug("The revocation request validation process was skipped " + "because the client_id parameter was missing or empty."); context.Skip(); return; } // Retrieve the application details corresponding to the requested client_id. var application = await Applications.FindByClientIdAsync(context.ClientId); if (application == null) { Logger.LogError("The revocation request was rejected because the client " + "application was not found: '{ClientId}'.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "The specified 'client_id' parameter is invalid."); return; } // Store the application entity as a request property to make it accessible // from the other provider methods without having to call the store twice. context.Request.SetProperty($"{OpenIddictConstants.Properties.Application}:{context.ClientId}", application); // Reject the request if the application is not allowed to use the revocation endpoint. if (!await Applications.HasPermissionAsync(application, OpenIddictConstants.Permissions.Endpoints.Revocation)) { Logger.LogError("The revocation request was rejected because the application '{ClientId}' " + "was not allowed to use the revocation endpoint.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.UnauthorizedClient, description: "This client application is not allowed to use the revocation endpoint."); return; } // Reject revocation requests containing a client_secret if the application is a public client. if (await Applications.IsPublicAsync(application)) { if (!string.IsNullOrEmpty(context.ClientSecret)) { Logger.LogError("The revocation request was rejected because the public application " + "'{ClientId}' was not allowed to use this endpoint.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The 'client_secret' parameter is not valid for this client application."); return; } Logger.LogDebug("The revocation request validation process was not fully validated because " + "the client '{ClientId}' was a public application.", context.ClientId); // If client authentication cannot be enforced, call context.Skip() to inform // the OpenID Connect server middleware that the caller cannot be fully trusted. context.Skip(); return; } // Confidential and hybrid applications MUST authenticate // to protect them from impersonation attacks. if (string.IsNullOrEmpty(context.ClientSecret)) { Logger.LogError("The revocation request was rejected because the confidential or hybrid application " + "'{ClientId}' didn't specify a client secret.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "The 'client_secret' parameter required for this client application is missing."); return; } if (!await Applications.ValidateClientSecretAsync(application, context.ClientSecret)) { Logger.LogError("The revocation request was rejected because the confidential or hybrid application " + "'{ClientId}' didn't specify valid client credentials.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "The specified client credentials are invalid."); return; } context.Validate(); }
public override async Task ValidateAuthorizationRequest([NotNull] ValidateAuthorizationRequestContext context) { var options = (OpenIddictOptions)context.Options; // Note: the OpenID Connect server middleware supports authorization code, implicit, hybrid, // none and custom flows but OpenIddict uses a stricter policy rejecting unknown flows. if (!context.Request.IsAuthorizationCodeFlow() && !context.Request.IsHybridFlow() && !context.Request.IsImplicitFlow() && !context.Request.IsNoneFlow()) { Logger.LogError("The authorization request was rejected because the '{ResponseType}' " + "response type is not supported.", context.Request.ResponseType); context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedResponseType, description: "The specified response_type parameter is not supported."); return; } // Reject code flow authorization requests if the authorization code flow is not enabled. if (context.Request.IsAuthorizationCodeFlow() && !options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode)) { Logger.LogError("The authorization request was rejected because " + "the authorization code flow was not enabled."); context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedResponseType, description: "The specified response_type parameter is not allowed."); return; } // Reject implicit flow authorization requests if the implicit flow is not enabled. if (context.Request.IsImplicitFlow() && !options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit)) { Logger.LogError("The authorization request was rejected because the implicit flow was not enabled."); context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedResponseType, description: "The specified response_type parameter is not allowed."); return; } // Reject hybrid flow authorization requests if the authorization code or the implicit flows are not enabled. if (context.Request.IsHybridFlow() && (!options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode) || !options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit))) { Logger.LogError("The authorization request was rejected because the " + "authorization code flow or the implicit flow was not enabled."); context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedResponseType, description: "The specified response_type parameter is not allowed."); return; } // Reject authorization requests that specify scope=offline_access if the refresh token flow is not enabled. if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && !options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The 'offline_access' scope is not allowed."); return; } // Note: the OpenID Connect server middleware supports the query, form_post and fragment response modes // and doesn't reject unknown/custom modes until the ApplyAuthorizationResponse event is invoked. // To ensure authorization requests are rejected early enough, an additional check is made by OpenIddict. if (!string.IsNullOrEmpty(context.Request.ResponseMode) && !context.Request.IsFormPostResponseMode() && !context.Request.IsFragmentResponseMode() && !context.Request.IsQueryResponseMode()) { Logger.LogError("The authorization request was rejected because the '{ResponseMode}' " + "response mode is not supported.", context.Request.ResponseMode); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The specified response_mode parameter is not supported."); return; } // Note: redirect_uri is not required for pure OAuth2 requests // but this provider uses a stricter policy making it mandatory, // as required by the OpenID Connect core specification. // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest. if (string.IsNullOrEmpty(context.RedirectUri)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The required redirect_uri parameter was missing."); return; } // Note: the OpenID Connect server middleware always ensures a // code_challenge_method can't be specified without code_challenge. if (!string.IsNullOrEmpty(context.Request.CodeChallenge)) { // Since the default challenge method (plain) is explicitly disallowed, // reject the authorization request if the code_challenge_method is missing. if (string.IsNullOrEmpty(context.Request.CodeChallengeMethod)) { Logger.LogError("The authorization request was rejected because the " + "required 'code_challenge_method' parameter was missing."); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The 'code_challenge_method' parameter must be specified."); return; } // Disallow the use of the unsecure code_challenge_method=plain method. // See https://tools.ietf.org/html/rfc7636#section-7.2 for more information. if (string.Equals(context.Request.CodeChallengeMethod, OpenIdConnectConstants.CodeChallengeMethods.Plain)) { Logger.LogError("The authorization request was rejected because the " + "'code_challenge_method' parameter was set to 'plain'."); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The specified code_challenge_method parameter is not allowed."); return; } // Reject authorization requests that contain response_type=token when a code_challenge is specified. if (context.Request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token)) { Logger.LogError("The authorization request was rejected because the " + "specified response type was not compatible with PKCE."); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The specified response_type parameter is not allowed when using PKCE."); return; } } // Retrieve the application details corresponding to the requested client_id. var application = await Applications.FindByClientIdAsync(context.ClientId, context.HttpContext.RequestAborted); if (application == null) { Logger.LogError("The authorization request was rejected because the client " + "application was not found: '{ClientId}'.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Application not found in the database: ensure that your client_id is correct."); return; } // Ensure a redirect_uri was associated with the application. if (!await Applications.HasRedirectUriAsync(application, context.HttpContext.RequestAborted)) { Logger.LogError("The authorization request was rejected because no redirect_uri " + "was registered with the application '{ClientId}'.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.UnauthorizedClient, description: "The client application is not allowed to use interactive flows."); return; } // Ensure the redirect_uri is valid. if (!await Applications.ValidateRedirectUriAsync(application, context.RedirectUri, context.HttpContext.RequestAborted)) { Logger.LogError("The authorization request was rejected because the redirect_uri " + "was invalid: '{RedirectUri}'.", context.RedirectUri); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Invalid redirect_uri."); return; } // To prevent downgrade attacks, ensure that authorization requests returning an access token directly // from the authorization endpoint are rejected if the client_id corresponds to a confidential application. // Note: when using the authorization code grant, ValidateTokenRequest is responsible of rejecting // the token request if the client_id corresponds to an unauthenticated confidential client. if (await Applications.IsConfidentialAsync(application, context.HttpContext.RequestAborted) && context.Request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "Confidential clients are not allowed to retrieve " + "an access token from the authorization endpoint."); return; } context.Validate(); }
public override async Task ValidateTokenRequest([NotNull] ValidateTokenRequestContext context) { var options = (OpenIddictOptions)context.Options; // Reject token requests that don't specify a supported grant type. if (!options.GrantTypes.Contains(context.Request.GrantType)) { Logger.LogError("The token request was rejected because the '{GrantType}' " + "grant type is not supported.", context.Request.GrantType); context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedGrantType, description: "The specified 'grant_type' parameter is not supported."); return; } // Reject token requests that specify scope=offline_access if the refresh token flow is not enabled. if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && !options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The 'offline_access' scope is not allowed."); return; } // Optimization: the OpenID Connect server middleware automatically rejects grant_type=authorization_code // requests missing the redirect_uri parameter if one was specified in the initial authorization request. // Since OpenIddict doesn't allow redirect_uri-less authorization requests, an earlier check can be made here, // which saves the OpenID Connect server middleware from having to deserialize the authorization code ticket. // See http://openid.net/specs/openid-connect-core-1_0.html#TokenRequestValidation for more information. if (context.Request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(context.Request.RedirectUri)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The mandatory 'redirect_uri' parameter is missing."); return; } // If the corresponding option was enabled, reject the request if scopes can't be validated. if (options.ValidateScopes && !await Scopes.ValidateScopesAsync( context.Request.GetScopes() .ToImmutableArray() .Remove(OpenIdConnectConstants.Scopes.OfflineAccess) .Remove(OpenIdConnectConstants.Scopes.OpenId))) { Logger.LogError("The token request was rejected because an unregistered scope was specified."); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The specified 'scope' parameter is not valid."); return; } // Note: the OpenID Connect server middleware allows returning a refresh token with grant_type=client_credentials, // though it's usually not recommended by the OAuth2 specification. To encourage developers to make a new // grant_type=client_credentials request instead of using refresh tokens, OpenIddict uses a stricter policy // that rejects grant_type=client_credentials requests containing the 'offline_access' scope. // See https://tools.ietf.org/html/rfc6749#section-4.4.3 for more information. if (context.Request.IsClientCredentialsGrantType() && context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The 'offline_access' scope is not valid for the specified 'grant_type' parameter."); return; } // Optimization: the OpenID Connect server middleware automatically rejects grant_type=client_credentials // requests when validation is skipped but an earlier check is made here to avoid making unnecessary // database roundtrips to retrieve the client application corresponding to the client_id. if (context.Request.IsClientCredentialsGrantType() && (string.IsNullOrEmpty(context.Request.ClientId) || string.IsNullOrEmpty(context.Request.ClientSecret))) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The 'client_id' and 'client_secret' parameters are " + "required when using the client credentials grant."); return; } // At this stage, skip client authentication if the client identifier is missing // or reject the token request if client identification is set as required. // Note: the OpenID Connect server middleware will automatically ensure that // the calling application cannot use an authorization code or a refresh token // if it's not the intended audience, even if client authentication was skipped. if (string.IsNullOrEmpty(context.ClientId)) { // Reject the request if client identification is mandatory. if (options.RequireClientIdentification) { Logger.LogError("The token request was rejected becaused the " + "mandatory client_id parameter was missing or empty."); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The mandatory 'client_id' parameter is missing."); return; } Logger.LogDebug("The token request validation process was partially skipped " + "because the 'client_id' parameter was missing or empty."); context.Skip(); return; } // Retrieve the application details corresponding to the requested client_id. var application = await Applications.FindByClientIdAsync(context.ClientId); if (application == null) { Logger.LogError("The token request was rejected because the client " + "application was not found: '{ClientId}'.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "The specified 'client_id' parameter is invalid."); return; } // Store the application entity as a request property to make it accessible // from the other provider methods without having to call the store twice. context.Request.SetProperty($"{OpenIddictConstants.Properties.Application}:{context.ClientId}", application); // Reject the request if the application is not allowed to use the token endpoint. if (!await Applications.HasPermissionAsync(application, OpenIddictConstants.Permissions.Endpoints.Token)) { Logger.LogError("The token request was rejected because the application '{ClientId}' " + "was not allowed to use the token endpoint.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.UnauthorizedClient, description: "This client application is not allowed to use the token endpoint."); return; } // Reject the request if the application is not allowed to use the specified grant type. if (!await Applications.HasPermissionAsync(application, OpenIddictConstants.Permissions.Prefixes.GrantType + context.Request.GrantType)) { Logger.LogError("The token request was rejected because the application '{ClientId}' was not allowed to " + "use the specified grant type: {GrantType}.", context.ClientId, context.Request.GrantType); context.Reject( error: OpenIdConnectConstants.Errors.UnauthorizedClient, description: "This client application is not allowed to use the specified grant type."); return; } if (await Applications.IsPublicAsync(application)) { // Note: public applications are not allowed to use the client credentials grant. if (context.Request.IsClientCredentialsGrantType()) { Logger.LogError("The token request was rejected because the public client application '{ClientId}' " + "was not allowed to use the client credentials grant.", context.Request.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.UnauthorizedClient, description: "The specified 'grant_type' parameter is not valid for this client application."); return; } // Reject token requests containing a client_secret when the client is a public application. if (!string.IsNullOrEmpty(context.ClientSecret)) { Logger.LogError("The token request was rejected because the public application '{ClientId}' " + "was not allowed to send a client secret.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The 'client_secret' parameter is not valid for this client application."); return; } Logger.LogDebug("The token request validation process was not fully validated because " + "the client '{ClientId}' was a public application.", context.ClientId); // If client authentication cannot be enforced, call context.Skip() to inform // the OpenID Connect server middleware that the caller cannot be fully trusted. context.Skip(); return; } // Confidential and hybrid applications MUST authenticate // to protect them from impersonation attacks. if (string.IsNullOrEmpty(context.ClientSecret)) { Logger.LogError("The token request was rejected because the confidential or hybrid application " + "'{ClientId}' didn't specify a client secret.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "The 'client_secret' parameter required for this client application is missing."); return; } if (!await Applications.ValidateClientSecretAsync(application, context.ClientSecret)) { Logger.LogError("The token request was rejected because the confidential or hybrid application " + "'{ClientId}' didn't specify valid client credentials.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "The specified client credentials are invalid."); return; } foreach (var scope in context.Request.GetScopes()) { // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes. if (string.Equals(scope, OpenIdConnectConstants.Scopes.OfflineAccess, StringComparison.Ordinal) || string.Equals(scope, OpenIdConnectConstants.Scopes.OpenId, StringComparison.Ordinal)) { continue; } // Reject the request if the application is not allowed to use the iterated scope. if (!await Applications.HasPermissionAsync(application, OpenIddictConstants.Permissions.Prefixes.Scope + scope)) { Logger.LogError("The token request was rejected because the application '{ClientId}' " + "was not allowed to use the scope {Scope}.", context.ClientId, scope); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "This client application is not allowed to use the specified scope."); return; } } context.Validate(); }
public override async Task ValidateAuthorizationRequest([NotNull] ValidateAuthorizationRequestContext context) { var options = (OpenIddictOptions)context.Options; // Note: the OpenID Connect server middleware supports authorization code, implicit, hybrid, // none and custom flows but OpenIddict uses a stricter policy rejecting none and custum flows. if (!context.Request.IsAuthorizationCodeFlow() && !context.Request.IsHybridFlow() && !context.Request.IsImplicitFlow()) { Logger.LogError("The authorization request was rejected because the '{ResponseType}' " + "response type is not supported.", context.Request.ResponseType); context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedResponseType, description: "The specified 'response_type' parameter is not supported."); return; } // Reject code flow authorization requests if the authorization code flow is not enabled. if (context.Request.IsAuthorizationCodeFlow() && !options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode)) { Logger.LogError("The authorization request was rejected because " + "the authorization code flow was not enabled."); context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedResponseType, description: "The specified 'response_type' parameter is not allowed."); return; } // Reject implicit flow authorization requests if the implicit flow is not enabled. if (context.Request.IsImplicitFlow() && !options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit)) { Logger.LogError("The authorization request was rejected because the implicit flow was not enabled."); context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedResponseType, description: "The specified 'response_type' parameter is not allowed."); return; } // Reject hybrid flow authorization requests if the authorization code or the implicit flows are not enabled. if (context.Request.IsHybridFlow() && (!options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.AuthorizationCode) || !options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.Implicit))) { Logger.LogError("The authorization request was rejected because the " + "authorization code flow or the implicit flow was not enabled."); context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedResponseType, description: "The specified 'response_type' parameter is not allowed."); return; } // Reject authorization requests that specify scope=offline_access if the refresh token flow is not enabled. if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && !options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The 'offline_access' scope is not allowed."); return; } // Validates scopes, unless scope validation was explicitly disabled. foreach (var scope in context.Request.GetScopes()) { if (options.EnableScopeValidation && !options.Scopes.Contains(scope) && await Scopes.FindByNameAsync(scope) == null) { Logger.LogError("The authorization request was rejected because an " + "unregistered scope was specified: {Scope}.", scope); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The specified 'scope' parameter is not valid."); return; } } // Note: the OpenID Connect server middleware supports the query, form_post and fragment response modes // and doesn't reject unknown/custom modes until the ApplyAuthorizationResponse event is invoked. // To ensure authorization requests are rejected early enough, an additional check is made by OpenIddict. if (!string.IsNullOrEmpty(context.Request.ResponseMode) && !context.Request.IsFormPostResponseMode() && !context.Request.IsFragmentResponseMode() && !context.Request.IsQueryResponseMode()) { Logger.LogError("The authorization request was rejected because the '{ResponseMode}' " + "response mode is not supported.", context.Request.ResponseMode); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The specified 'response_mode' parameter is not supported."); return; } // Note: redirect_uri is not required for pure OAuth2 requests // but this provider uses a stricter policy making it mandatory, // as required by the OpenID Connect core specification. // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest. if (string.IsNullOrEmpty(context.RedirectUri)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The mandatory 'redirect_uri' parameter is missing."); return; } // Note: the OpenID Connect server middleware always ensures a // code_challenge_method can't be specified without code_challenge. if (!string.IsNullOrEmpty(context.Request.CodeChallenge)) { // Since the default challenge method (plain) is explicitly disallowed, // reject the authorization request if the code_challenge_method is missing. if (string.IsNullOrEmpty(context.Request.CodeChallengeMethod)) { Logger.LogError("The authorization request was rejected because the " + "required 'code_challenge_method' parameter was missing."); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The 'code_challenge_method' parameter must be specified."); return; } // Disallow the use of the unsecure code_challenge_method=plain method. // See https://tools.ietf.org/html/rfc7636#section-7.2 for more information. if (string.Equals(context.Request.CodeChallengeMethod, OpenIdConnectConstants.CodeChallengeMethods.Plain)) { Logger.LogError("The authorization request was rejected because the " + "'code_challenge_method' parameter was set to 'plain'."); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The specified 'code_challenge_method' parameter is not allowed."); return; } // Reject authorization requests that contain response_type=token when a code_challenge is specified. if (context.Request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token)) { Logger.LogError("The authorization request was rejected because the " + "specified response type was not compatible with PKCE."); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The specified 'response_type' parameter is not allowed when using PKCE."); return; } } // Retrieve the application details corresponding to the requested client_id. var application = await Applications.FindByClientIdAsync(context.ClientId); if (application == null) { Logger.LogError("The authorization request was rejected because the client " + "application was not found: '{ClientId}'.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The specified 'client_id' parameter is invalid."); return; } // Store the application entity as a request property to make it accessible // from the other provider methods without having to call the store twice. context.Request.SetProperty($"{OpenIddictConstants.Properties.Application}:{context.ClientId}", application); // To prevent downgrade attacks, ensure that authorization requests returning a token directly from // the authorization endpoint are rejected if the client_id corresponds to a confidential application. // Note: when using the authorization code grant, ValidateTokenRequest is responsible of rejecting // the token request if the client_id corresponds to an unauthenticated confidential client. if (await Applications.IsConfidentialAsync(application) && (context.Request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) || context.Request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token))) { context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedResponseType, description: "The specified 'response_type' parameter is not valid for this client application."); return; } // Reject the request if the application is not allowed to use the authorization endpoint. if (!await Applications.HasPermissionAsync(application, OpenIddictConstants.Permissions.Endpoints.Authorization)) { Logger.LogError("The authorization request was rejected because the application '{ClientId}' " + "was not allowed to use the authorization endpoint.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.UnauthorizedClient, description: "This client application is not allowed to use the authorization endpoint."); return; } // Reject the request if the application is not allowed to use the authorization code flow. if (context.Request.IsAuthorizationCodeFlow() && !await Applications.HasPermissionAsync( application, OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode)) { Logger.LogError("The authorization request was rejected because the application '{ClientId}' " + "was not allowed to use the authorization code flow.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.UnauthorizedClient, description: "The client application is not allowed to use the authorization code flow."); return; } // Reject the request if the application is not allowed to use the implicit flow. if (context.Request.IsImplicitFlow() && !await Applications.HasPermissionAsync( application, OpenIddictConstants.Permissions.GrantTypes.Implicit)) { Logger.LogError("The authorization request was rejected because the application '{ClientId}' " + "was not allowed to use the implicit flow.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.UnauthorizedClient, description: "The client application is not allowed to use the implicit flow."); return; } // Reject the request if the application is not allowed to use the authorization code/implicit flows. if (context.Request.IsHybridFlow() && (!await Applications.HasPermissionAsync(application, OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode) || !await Applications.HasPermissionAsync(application, OpenIddictConstants.Permissions.GrantTypes.Implicit))) { Logger.LogError("The authorization request was rejected because the application '{ClientId}' " + "was not allowed to use the hybrid flow.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.UnauthorizedClient, description: "The client application is not allowed to use the hybrid flow."); return; } // Reject the request if the offline_access scope was request and if the // application is not allowed to use the authorization code/implicit flows. if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && !await Applications.HasPermissionAsync(application, OpenIddictConstants.Permissions.GrantTypes.RefreshToken)) { Logger.LogError("The authorization request was rejected because the application '{ClientId}' " + "was not allowed to request the 'offline_access' scope.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The client application is not allowed to use the 'offline_access' scope."); return; } // Ensure that the specified redirect_uri is valid and is associated with the client application. if (!await Applications.ValidateRedirectUriAsync(application, context.RedirectUri)) { Logger.LogError("The authorization request was rejected because the redirect_uri " + "was invalid: '{RedirectUri}'.", context.RedirectUri); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The specified 'redirect_uri' parameter is not valid for this client application."); return; } foreach (var scope in context.Request.GetScopes()) { // Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes. if (string.Equals(scope, OpenIdConnectConstants.Scopes.OfflineAccess, StringComparison.Ordinal) || string.Equals(scope, OpenIdConnectConstants.Scopes.OpenId, StringComparison.Ordinal)) { continue; } // Reject the request if the application is not allowed to use the iterated scope. if (!await Applications.HasPermissionAsync(application, OpenIddictConstants.Permissions.Prefixes.Scope + scope)) { Logger.LogError("The authorization request was rejected because the application '{ClientId}' " + "was not allowed to use the scope {Scope}.", context.ClientId, scope); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "This client application is not allowed to use the specified scope."); return; } } context.Validate(); }
public override async Task ValidateRevocationRequest([NotNull] ValidateRevocationRequestContext context) { var options = (OpenIddictOptions)context.Options; Debug.Assert(!options.DisableTokenRevocation, "Token revocation support shouldn't be disabled at this stage."); // When token_type_hint is specified, reject the request if it doesn't correspond to a revocable token. if (!string.IsNullOrEmpty(context.Request.TokenTypeHint) && !string.Equals(context.Request.TokenTypeHint, OpenIdConnectConstants.TokenTypeHints.AuthorizationCode) && !string.Equals(context.Request.TokenTypeHint, OpenIdConnectConstants.TokenTypeHints.RefreshToken)) { context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedTokenType, description: "Only authorization codes and refresh tokens can be revoked. When specifying a token_type_hint " + "parameter, its value must be equal to 'authorization_code' or 'refresh_token'."); return; } // Skip client authentication if the client identifier is missing or reject // the revocation request if client identification is set as required. // Note: the OpenID Connect server middleware will automatically ensure that // the calling application cannot revoke a refresh token if it's not // the intended audience, even if client authentication was skipped. if (string.IsNullOrEmpty(context.ClientId)) { // Reject the request if client identification is mandatory. if (options.RequireClientIdentification) { Logger.LogError("The revocation request was rejected becaused the " + "mandatory client_id parameter was missing or empty."); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The mandatory 'client_id' parameter was missing."); return; } Logger.LogInformation("The revocation request validation process was skipped " + "because the client_id parameter was missing or empty."); context.Skip(); return; } // Retrieve the application details corresponding to the requested client_id. var application = await Applications.FindByClientIdAsync(context.ClientId, context.HttpContext.RequestAborted); if (application == null) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Application not found in the database: ensure that your client_id is correct."); return; } // Reject revocation requests containing a client_secret if the client application is not confidential. if (await Applications.IsPublicAsync(application, context.HttpContext.RequestAborted)) { // Reject tokens requests containing a client_secret when the client is a public application. if (!string.IsNullOrEmpty(context.ClientSecret)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "Public clients are not allowed to send a client_secret."); return; } Logger.LogInformation("The revocation request validation process was not fully validated because " + "the client '{ClientId}' was a public application.", context.ClientId); // If client authentication cannot be enforced, call context.Skip() to inform // the OpenID Connect server middleware that the caller cannot be fully trusted. context.Skip(); return; } // Confidential applications MUST authenticate // to protect them from impersonation attacks. if (string.IsNullOrEmpty(context.ClientSecret)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Missing credentials: ensure that you specified a client_secret."); return; } if (!await Applications.ValidateClientSecretAsync(application, context.ClientSecret, context.HttpContext.RequestAborted)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Invalid credentials: ensure that you specified a correct client_secret."); return; } context.Validate(); }
public override async Task ValidateTokenRequest([NotNull] ValidateTokenRequestContext context) { var options = (OpenIddictOptions)context.Options; // Reject token requests that don't specify a supported grant type. if (!options.GrantTypes.Contains(context.Request.GrantType)) { Logger.LogError("The token request was rejected because the '{Grant}' " + "grant is not supported.", context.Request.GrantType); context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedGrantType, description: "The specified grant_type is not supported by this authorization server."); return; } // Reject token requests that specify scope=offline_access if the refresh token flow is not enabled. if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && !options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The 'offline_access' scope is not allowed."); return; } // Optimization: the OpenID Connect server middleware automatically rejects grant_type=authorization_code // requests missing the redirect_uri parameter if one was specified in the initial authorization request. // Since OpenIddict doesn't allow redirect_uri-less authorization requests, an earlier check can be made here, // which saves the OpenID Connect server middleware from having to deserialize the authorization code ticket. // See http://openid.net/specs/openid-connect-core-1_0.html#TokenRequestValidation for more information. if (context.Request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(context.Request.RedirectUri)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The mandatory 'redirect_uri' parameter was missing."); return; } // Note: the OpenID Connect server middleware allows returning a refresh token with grant_type=client_credentials, // though it's usually not recommended by the OAuth2 specification. To encourage developers to make a new // grant_type=client_credentials request instead of using refresh tokens, OpenIddict uses a stricter policy // that rejects grant_type=client_credentials requests containing the 'offline_access' scope. // See https://tools.ietf.org/html/rfc6749#section-4.4.3 for more information. if (context.Request.IsClientCredentialsGrantType() && context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The 'offline_access' scope is not allowed when using grant_type=client_credentials."); return; } // Optimization: the OpenID Connect server middleware automatically rejects grant_type=client_credentials // requests when validation is skipped but an earlier check is made here to avoid making unnecessary // database roundtrips to retrieve the client application corresponding to the client_id. if (context.Request.IsClientCredentialsGrantType() && (string.IsNullOrEmpty(context.Request.ClientId) || string.IsNullOrEmpty(context.Request.ClientSecret))) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "Client applications must be authenticated to use the client credentials grant."); return; } // At this stage, skip client authentication if the client identifier is missing // or reject the token request if client identification is set as required. // Note: the OpenID Connect server middleware will automatically ensure that // the calling application cannot use an authorization code or a refresh token // if it's not the intended audience, even if client authentication was skipped. if (string.IsNullOrEmpty(context.ClientId)) { // Reject the request if client identification is mandatory. if (options.RequireClientIdentification) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The mandatory 'client_id' parameter was missing."); return; } Logger.LogDebug("The token request validation process was partially skipped " + "because the 'client_id' parameter was missing or empty."); context.Skip(); return; } // Retrieve the application details corresponding to the requested client_id. var application = await Applications.FindByClientIdAsync(context.ClientId, context.HttpContext.RequestAborted); if (application == null) { Logger.LogError("The token request was rejected because the client " + "application was not found: '{ClientId}'.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Application not found in the database: ensure that your client_id is correct."); return; } if (await Applications.IsPublicAsync(application, context.HttpContext.RequestAborted)) { // Note: public applications are not allowed to use the client credentials grant. if (context.Request.IsClientCredentialsGrantType()) { Logger.LogError("The token request was rejected because the public client application '{ClientId}' " + "was not allowed to use the client credentials grant.", context.Request.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.UnauthorizedClient, description: "Public clients are not allowed to use the client credentials grant."); return; } // Reject tokens requests containing a client_secret when the client is a public application. if (!string.IsNullOrEmpty(context.ClientSecret)) { Logger.LogError("The token request was rejected because the public application '{ClientId}' " + "was not allowed to send a client secret.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "Public clients are not allowed to send a client_secret."); return; } Logger.LogInformation("The token request validation process was not fully validated because " + "the client '{ClientId}' was a public application.", context.ClientId); // If client authentication cannot be enforced, call context.Skip() to inform // the OpenID Connect server middleware that the caller cannot be fully trusted. context.Skip(); return; } // Confidential applications MUST authenticate // to protect them from impersonation attacks. if (string.IsNullOrEmpty(context.ClientSecret)) { Logger.LogError("The token request was rejected because the confidential application " + "'{ClientId}' didn't specify a client secret.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Missing credentials: ensure that you specified a client_secret."); return; } if (!await Applications.ValidateClientSecretAsync(application, context.ClientSecret, context.HttpContext.RequestAborted)) { Logger.LogError("The token request was rejected because the confidential application " + "'{ClientId}' didn't specify valid client credentials.", context.ClientId); context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Invalid credentials: ensure that you specified a correct client_secret."); return; } context.Validate(); }