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