Esempio n. 1
0
            /// <summary>
            /// Processes the event.
            /// </summary>
            /// <param name="context">The context associated with the event to process.</param>
            /// <returns>
            /// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
            /// </returns>
            public async Task HandleAsync([NotNull] ProcessSigninResponseContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException(nameof(context));
                }

                var principal = context.Principal.Clone(_ => true)
                                .SetTokenId(Guid.NewGuid().ToString())
                                .SetCreationDate(DateTimeOffset.UtcNow);

                var lifetime = context.Principal.GetRefreshTokenLifetime() ?? context.Options.RefreshTokenLifetime;

                if (lifetime.HasValue)
                {
                    principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
                }

                var notification = new SerializeRefreshTokenContext(context.Transaction)
                {
                    Principal = principal
                };

                await _provider.DispatchAsync(notification);

                if (!notification.IsHandled)
                {
                    throw new InvalidOperationException(new StringBuilder()
                                                        .Append("The refresh token was not correctly processed. This may indicate ")
                                                        .Append("that the event handler responsible of generating refresh tokens ")
                                                        .Append("was not registered or was explicitly removed from the handlers list.")
                                                        .ToString());
                }

                context.Response.RefreshToken = notification.Token;
            }
        public override async Task ProcessSigninResponse([NotNull] ProcessSigninResponseContext context)
        {
            var options = (OpenIddictServerOptions)context.Options;

            Debug.Assert(context.Request.IsAuthorizationRequest() ||
                         context.Request.IsTokenRequest(),
                         "The request should be an authorization or token request.");

            if (context.Request.IsTokenRequest() && (context.Request.IsAuthorizationCodeGrantType() ||
                                                     context.Request.IsRefreshTokenGrantType()))
            {
                // Note: when handling a grant_type=authorization_code or refresh_token request,
                // the OpenID Connect server middleware allows creating authentication tickets
                // that are completely disconnected from the original code or refresh token ticket.
                // This scenario is deliberately not supported in OpenIddict and all the tickets
                // must be linked. To ensure the properties are flowed from the authorization code
                // or the refresh token to the new ticket, they are manually restored if necessary.
                if (!context.Ticket.Properties.HasProperty(OpenIdConnectConstants.Properties.TokenId))
                {
                    // Retrieve the original authentication ticket from the request properties.
                    var ticket = context.Request.GetProperty <AuthenticationTicket>(
                        OpenIddictConstants.Properties.AuthenticationTicket);
                    Debug.Assert(ticket != null, "The authentication ticket shouldn't be null.");

                    foreach (var property in ticket.Properties.Items)
                    {
                        // Don't override the properties that have been
                        // manually set on the new authentication ticket.
                        if (context.Ticket.HasProperty(property.Key))
                        {
                            continue;
                        }

                        context.Ticket.AddProperty(property.Key, property.Value);
                    }

                    // Always include the "openid" scope when the developer doesn't explicitly call SetScopes.
                    // Note: the application is allowed to specify a different "scopes": in this case,
                    // don't replace the "scopes" property stored in the authentication ticket.
                    if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OpenId) && !context.Ticket.HasScope())
                    {
                        context.Ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId);
                    }

                    context.IncludeIdentityToken = context.Ticket.HasScope(OpenIdConnectConstants.Scopes.OpenId);
                }

                context.IncludeRefreshToken = context.Ticket.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess);

                // Always include a refresh token for grant_type=refresh_token requests if
                // rolling tokens are enabled and if the offline_access scope was specified.
                if (context.Request.IsRefreshTokenGrantType())
                {
                    context.IncludeRefreshToken &= options.UseRollingTokens;
                }

                // If token revocation was explicitly disabled,
                // none of the following security routines apply.
                if (options.DisableTokenRevocation)
                {
                    await base.ProcessSigninResponse(context);

                    return;
                }

                var token = context.Request.GetProperty($"{OpenIddictConstants.Properties.Token}:{context.Ticket.GetTokenId()}");
                Debug.Assert(token != null, "The token shouldn't be null.");

                // If rolling tokens are enabled or if the request is a grant_type=authorization_code request,
                // mark the authorization code or the refresh token as redeemed to prevent future reuses.
                // If the operation fails, return an error indicating the code/token is no longer valid.
                // See https://tools.ietf.org/html/rfc6749#section-6 for more information.
                if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType())
                {
                    if (!await TryRedeemTokenAsync(token))
                    {
                        context.Reject(
                            error: OpenIdConnectConstants.Errors.InvalidGrant,
                            description: context.Request.IsAuthorizationCodeGrantType() ?
                            "The specified authorization code is no longer valid." :
                            "The specified refresh token is no longer valid.");

                        return;
                    }
                }

                if (context.Request.IsRefreshTokenGrantType())
                {
                    // When rolling tokens are enabled, try to revoke all the previously issued tokens
                    // associated with the authorization if the request is a refresh_token request.
                    // If the operation fails, silently ignore the error and keep processing the request:
                    // this may indicate that one of the revoked tokens was modified by a concurrent request.
                    if (options.UseRollingTokens)
                    {
                        await TryRevokeTokensAsync(context.Ticket);
                    }

                    // When rolling tokens are disabled, try to extend the expiration date
                    // of the existing token instead of returning a new refresh token
                    // with a new expiration date if sliding expiration was not disabled.
                    // If the operation fails, silently ignore the error and keep processing
                    // the request: this may indicate that a concurrent refresh token request
                    // already updated the expiration date associated with the refresh token.
                    if (!options.UseRollingTokens && options.UseSlidingExpiration)
                    {
                        await TryExtendTokenAsync(token, context.Ticket, options);
                    }
                }
            }

            // If no authorization was explicitly attached to the authentication ticket,
            // create an ad hoc authorization if an authorization code or a refresh token
            // is going to be returned to the client application as part of the response.
            if (!context.Ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId) &&
                (context.IncludeAuthorizationCode || context.IncludeRefreshToken))
            {
                await CreateAuthorizationAsync(context.Ticket, options, context.Request);
            }

            // Add the custom properties that are marked as public as authorization or
            // token response properties and remove them from the authentication ticket
            // so they are not persisted in the authorization code/access/refresh token.
            // Note: make sure the foreach statement iterates on a copy of the ticket
            // as the property collection is modified when the property is removed.
            var parameters = GetParameters(context.Request, context.Ticket.Properties);

            foreach (var(property, parameter, value) in parameters.ToArray())
            {
                context.Response.AddParameter(parameter, value);
                context.Ticket.RemoveProperty(property);
            }

            await base.ProcessSigninResponse(context);
        }
Esempio n. 3
0
            /// <summary>
            /// Processes the event.
            /// </summary>
            /// <param name="context">The context associated with the event to process.</param>
            /// <returns>
            /// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
            /// </returns>
            public async Task HandleAsync([NotNull] ProcessSigninResponseContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException(nameof(context));
                }

                var principal = context.Principal.Clone(_ => true)
                                .SetTokenId(Guid.NewGuid().ToString())
                                .SetCreationDate(DateTimeOffset.UtcNow);

                var lifetime = context.Principal.GetAuthorizationCodeLifetime() ?? context.Options.AuthorizationCodeLifetime;

                if (lifetime.HasValue)
                {
                    principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
                }

                // Attach the redirect_uri to allow for later comparison when
                // receiving a grant_type=authorization_code token request.
                if (!string.IsNullOrEmpty(context.Request.RedirectUri))
                {
                    principal.SetClaim(Claims.Private.OriginalRedirectUri, context.Request.RedirectUri);
                }

                // Attach the code challenge and the code challenge methods to allow the ValidateCodeVerifier
                // handler to validate the code verifier sent by the client as part of the token request.
                if (!string.IsNullOrEmpty(context.Request.CodeChallenge))
                {
                    principal.SetClaim(Claims.Private.CodeChallenge, context.Request.CodeChallenge);

                    // Default to S256 if no explicit code challenge method was specified.
                    principal.SetClaim(Claims.Private.CodeChallengeMethod,
                                       !string.IsNullOrEmpty(context.Request.CodeChallengeMethod) ?
                                       context.Request.CodeChallengeMethod : CodeChallengeMethods.Sha256);
                }

                // Attach the nonce so that it can be later returned by
                // the token endpoint as part of the JWT identity token.
                if (!string.IsNullOrEmpty(context.Request.Nonce))
                {
                    principal.SetClaim(Claims.Nonce, context.Request.Nonce);
                }

                var notification = new SerializeAuthorizationCodeContext(context.Transaction)
                {
                    Principal = principal
                };

                await _provider.DispatchAsync(notification);

                if (!notification.IsHandled)
                {
                    throw new InvalidOperationException(new StringBuilder()
                                                        .Append("The authorization code was not correctly processed. This may indicate ")
                                                        .Append("that the event handler responsible of generating authorization codes ")
                                                        .Append("was not registered or was explicitly removed from the handlers list.")
                                                        .ToString());
                }

                context.Response.Code = notification.Token;
            }
        public override async Task ProcessSigninResponse([NotNull] ProcessSigninResponseContext context)
        {
            var options = (OpenIddictOptions)context.Options;

            if (context.Request.IsTokenRequest() && (context.Request.IsAuthorizationCodeGrantType() ||
                                                     context.Request.IsRefreshTokenGrantType()))
            {
                // Note: when handling a grant_type=authorization_code or refresh_token request,
                // the OpenID Connect server middleware allows creating authentication tickets
                // that are completely disconnected from the original code or refresh token ticket.
                // This scenario is deliberately not supported in OpenIddict and all the tickets
                // must be linked. To ensure the properties are preserved from an authorization code
                // or a refresh token to the new ticket, they are manually restored if necessary.

                // Retrieve the original authentication ticket from the request properties.
                var ticket = context.Request.GetProperty <AuthenticationTicket>(
                    OpenIddictConstants.Properties.AuthenticationTicket);
                Debug.Assert(ticket != null, "The authentication ticket shouldn't be null.");

                // If the properties instances of the two authentication tickets differ,
                // restore the missing properties in the new authentication ticket.
                if (!ReferenceEquals(ticket.Properties, context.Ticket.Properties))
                {
                    foreach (var property in ticket.Properties.Items)
                    {
                        // Don't override the properties that have been
                        // manually set on the new authentication ticket.
                        if (context.Ticket.HasProperty(property.Key))
                        {
                            continue;
                        }

                        context.Ticket.AddProperty(property.Key, property.Value);
                    }

                    // Always include the "openid" scope when the developer doesn't explicitly call SetScopes.
                    // Note: the application is allowed to specify a different "scopes": in this case,
                    // don't replace the "scopes" property stored in the authentication ticket.
                    if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OpenId) && !context.Ticket.HasScope())
                    {
                        context.Ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId);
                    }

                    context.IncludeIdentityToken = context.Ticket.HasScope(OpenIdConnectConstants.Scopes.OpenId);
                }

                // Always include a refresh token for grant_type=refresh_token requests if
                // rolling tokens are enabled and if the offline_access scope was specified.
                context.IncludeRefreshToken = context.Request.IsRefreshTokenGrantType() && options.UseRollingTokens &&
                                              context.Ticket.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess);

                // If token revocation was explicitly disabled,
                // none of the following security routines apply.
                if (options.DisableTokenRevocation)
                {
                    return;
                }

                // If rolling tokens are enabled or if the request is a grant_type=authorization_code request,
                // mark the authorization code or the refresh token as redeemed to prevent future reuses.
                // See https://tools.ietf.org/html/rfc6749#section-6 for more information.
                if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType())
                {
                    if (!await TryRedeemTokenAsync(context.Ticket, context.HttpContext))
                    {
                        context.Reject(
                            error: OpenIdConnectConstants.Errors.InvalidGrant,
                            description: "The specified authorization code is no longer valid.");

                        return;
                    }
                }

                // When rolling tokens are enabled, revoke all the previously issued tokens associated
                // with the authorization if the request is a grant_type=refresh_token request.
                if (options.UseRollingTokens && context.Request.IsRefreshTokenGrantType())
                {
                    if (!await TryRevokeTokensAsync(context.Ticket, context.HttpContext))
                    {
                        context.Reject(
                            error: OpenIdConnectConstants.Errors.InvalidGrant,
                            description: "The specified refresh token is no longer valid.");

                        return;
                    }
                }

                // When rolling tokens are disabled, extend the expiration date
                // of the existing token instead of returning a new refresh token
                // with a new expiration date if sliding expiration was not disabled.
                else if (options.UseSlidingExpiration && context.Request.IsRefreshTokenGrantType())
                {
                    if (!await TryExtendTokenAsync(context.Ticket, context.HttpContext, options))
                    {
                        context.Reject(
                            error: OpenIdConnectConstants.Errors.InvalidGrant,
                            description: "The specified refresh token is no longer valid.");

                        return;
                    }

                    // Prevent the OpenID Connect server from returning a new refresh token.
                    context.IncludeRefreshToken = false;
                }
            }

            // If no authorization was explicitly attached to the authentication ticket,
            // create an ad hoc authorization if an authorization code or a refresh token
            // is going to be returned to the client application as part of the response.
            if (!context.Ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId) &&
                (context.IncludeAuthorizationCode || context.IncludeRefreshToken))
            {
                await CreateAuthorizationAsync(context.Ticket, options, context.HttpContext, context.Request);
            }
        }
Esempio n. 5
0
 public override Task ProcessSigninResponse(ProcessSigninResponseContext context)
 {
     context.IncludeRefreshToken = true;
     return(Task.CompletedTask);
 }
        public override async Task ProcessSigninResponse([NotNull] ProcessSigninResponseContext context)
        {
            var options = (KeystoneServerOptions)context.Options;

            Debug.Assert(context.Request.IsAuthorizationRequest() ||
                         context.Request.IsTokenRequest(),
                         "The request should be an authorization or token request.");

            // While null/unauthenticated identities can be validly represented and are allowed by
            // the OpenID Connect server handler, this most likely indicates that the developer
            // has not correctly set the authentication type associated with the claims identity,
            // which may later cause issues when validating opaque access tokens, as the resulting
            // principal would be considered unauthenticated by the ASP.NET Core authorization stack.
            if (context.Ticket.Principal.Identity == null || !context.Ticket.Principal.Identity.IsAuthenticated)
            {
                throw new InvalidOperationException(new StringBuilder()
                                                    .AppendLine("The specified principal doesn't contain a valid or authenticated identity.")
                                                    .Append("Make sure that both 'ClaimsPrincipal.Identity' and 'ClaimsPrincipal.Identity.AuthenticationType' ")
                                                    .Append("are not null and that 'ClaimsPrincipal.Identity.IsAuthenticated' returns 'true'.")
                                                    .ToString());
            }

            if (context.Request.IsTokenRequest() && (context.Request.IsAuthorizationCodeGrantType() ||
                                                     context.Request.IsRefreshTokenGrantType()))
            {
                // Note: when handling a grant_type=authorization_code or refresh_token request,
                // the OpenID Connect server middleware allows creating authentication tickets
                // that are completely disconnected from the original code or refresh token ticket.
                // This scenario is deliberately not supported in Keystone and all the tickets
                // must be linked. To ensure the properties are flowed from the authorization code
                // or the refresh token to the new ticket, they are manually restored if necessary.
                if (string.IsNullOrEmpty(context.Ticket.GetInternalTokenId()))
                {
                    // Retrieve the original authentication ticket from the request properties.
                    var ticket = context.Request.GetProperty <AuthenticationTicket>(
                        KeystoneConstants.Properties.AuthenticationTicket);
                    Debug.Assert(ticket != null, "The authentication ticket shouldn't be null.");

                    foreach (var property in ticket.Properties.Items)
                    {
                        // Don't override the properties that have been
                        // manually set on the new authentication ticket.
                        if (context.Ticket.HasProperty(property.Key))
                        {
                            continue;
                        }

                        context.Ticket.AddProperty(property.Key, property.Value);
                    }

                    // Always include the "openid" scope when the developer doesn't explicitly call SetScopes.
                    // Note: the application is allowed to specify a different "scopes": in this case,
                    // don't replace the "scopes" property stored in the authentication ticket.
                    if (context.Request.HasScope(KeystoneConstants.Scopes.OpenId) && !context.Ticket.HasScope())
                    {
                        context.Ticket.SetScopes(KeystoneConstants.Scopes.OpenId);
                    }

                    context.IncludeIdentityToken = context.Ticket.HasScope(KeystoneConstants.Scopes.OpenId);
                }

                context.IncludeRefreshToken = context.Ticket.HasScope(KeystoneConstants.Scopes.OfflineAccess);

                // Always include a refresh token for grant_type=refresh_token requests if
                // rolling tokens are enabled and if the offline_access scope was specified.
                if (context.Request.IsRefreshTokenGrantType())
                {
                    context.IncludeRefreshToken &= options.UseRollingTokens;
                }

                // If token revocation was explicitly disabled, none of the following security routines apply.
                if (!options.DisableTokenStorage)
                {
                    var token = await _tokenManager.FindByIdAsync(context.Ticket.GetInternalTokenId());

                    if (token == null)
                    {
                        context.Reject(
                            error: KeystoneConstants.Errors.InvalidGrant,
                            description: context.Request.IsAuthorizationCodeGrantType() ?
                            "The specified authorization code is no longer valid." :
                            "The specified refresh token is no longer valid.");

                        return;
                    }

                    // If rolling tokens are enabled or if the request is a grant_type=authorization_code request,
                    // mark the authorization code or the refresh token as redeemed to prevent future reuses.
                    // If the operation fails, return an error indicating the code/token is no longer valid.
                    // See https://tools.ietf.org/html/rfc6749#section-6 for more information.
                    if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType())
                    {
                        if (!await TryRedeemTokenAsync(token))
                        {
                            context.Reject(
                                error: KeystoneConstants.Errors.InvalidGrant,
                                description: context.Request.IsAuthorizationCodeGrantType() ?
                                "The specified authorization code is no longer valid." :
                                "The specified refresh token is no longer valid.");

                            return;
                        }
                    }

                    if (context.Request.IsRefreshTokenGrantType())
                    {
                        // When rolling tokens are enabled, try to revoke all the previously issued tokens
                        // associated with the authorization if the request is a refresh_token request.
                        // If the operation fails, silently ignore the error and keep processing the request:
                        // this may indicate that one of the revoked tokens was modified by a concurrent request.
                        if (options.UseRollingTokens)
                        {
                            await TryRevokeTokensAsync(context.Ticket);
                        }

                        // When rolling tokens are disabled, try to extend the expiration date
                        // of the existing token instead of returning a new refresh token
                        // with a new expiration date if sliding expiration was not disabled.
                        // If the operation fails, silently ignore the error and keep processing
                        // the request: this may indicate that a concurrent refresh token request
                        // already updated the expiration date associated with the refresh token.
                        if (!options.UseRollingTokens && options.UseSlidingExpiration)
                        {
                            await TryExtendRefreshTokenAsync(token, context.Ticket, options);
                        }
                    }
                }
            }

            // If no authorization was explicitly attached to the authentication ticket,
            // create an ad hoc authorization if an authorization code or a refresh token
            // is going to be returned to the client application as part of the response.
            if (!options.DisableAuthorizationStorage &&
                string.IsNullOrEmpty(context.Ticket.GetInternalAuthorizationId()) &&
                (context.IncludeAuthorizationCode || context.IncludeRefreshToken))
            {
                await CreateAuthorizationAsync(context.Ticket, options, context.Request);
            }

            // Add the custom properties that are marked as public as authorization or
            // token response properties and remove them from the authentication ticket
            // so they are not persisted in the authorization code/access/refresh token.
            // Note: make sure the foreach statement iterates on a copy of the ticket
            // as the property collection is modified when the property is removed.
            var parameters = GetParameters(context.Request, context.Ticket.Properties);

            foreach (var(property, parameter, value) in parameters.ToList())
            {
                context.Response.AddParameter(parameter, value);
                context.Ticket.RemoveProperty(property);
            }

            await _eventDispatcher.DispatchAsync(new KeystoneServerEvents.ProcessSigninResponse(context));
        }