Exemple #1
0
        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();
        }
Exemple #3
0
        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();
        }
Exemple #5
0
        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);
        }
Exemple #6
0
        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();
        }
Exemple #7
0
        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();
        }
Exemple #8
0
        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();
        }
Exemple #9
0
        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();
        }
Exemple #10
0
        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();
        }
Exemple #11
0
        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();
        }