Ejemplo n.º 1
0
        private async Task <string> SerializeAuthorizationCodeAsync(
            ClaimsIdentity identity, AuthenticationProperties properties,
            OpenIdConnectRequest request, OpenIdConnectResponse response)
        {
            // Note: claims in authorization codes are never filtered as they are supposed to be opaque:
            // SerializeAccessTokenAsync and SerializeIdentityTokenAsync are responsible of ensuring
            // that subsequent access and identity tokens are correctly filtered.

            // Create a new ticket containing the updated properties.
            var ticket = new AuthenticationTicket(identity, properties);

            ticket.Properties.IssuedUtc  = Options.SystemClock.UtcNow;
            ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc +
                                           (ticket.GetAuthorizationCodeLifetime() ?? Options.AuthorizationCodeLifetime);

            ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode);

            // Associate a random identifier with the authorization code.
            ticket.SetTicketId(Guid.NewGuid().ToString());

            // Store the code_challenge, code_challenge_method and nonce parameters for later comparison.
            ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallenge, request.CodeChallenge)
            .SetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod, request.CodeChallengeMethod)
            .SetProperty(OpenIdConnectConstants.Properties.Nonce, request.Nonce);

            // Store the original redirect_uri sent by the client application for later comparison.
            ticket.SetProperty(OpenIdConnectConstants.Properties.RedirectUri,
                               request.GetProperty <string>(OpenIdConnectConstants.Properties.OriginalRedirectUri));

            // Remove the unwanted properties from the authentication ticket.
            ticket.RemoveProperty(OpenIdConnectConstants.Properties.AuthorizationCodeLifetime)
            .RemoveProperty(OpenIdConnectConstants.Properties.ClientId);

            var notification = new SerializeAuthorizationCodeContext(Context, Options, request, response, ticket)
            {
                DataFormat = Options.AuthorizationCodeFormat
            };

            await Options.Provider.SerializeAuthorizationCode(notification);

            if (notification.HandledResponse || !string.IsNullOrEmpty(notification.AuthorizationCode))
            {
                return(notification.AuthorizationCode);
            }

            else if (notification.Skipped)
            {
                return(null);
            }

            if (notification.DataFormat == null)
            {
                return(null);
            }

            return(notification.DataFormat.Protect(ticket));
        }
        private async Task <string> SerializeAuthorizationCodeAsync(
            ClaimsIdentity identity, AuthenticationProperties properties,
            OpenIdConnectRequest request, OpenIdConnectResponse response)
        {
            // Note: claims in authorization codes are never filtered as they are supposed to be opaque:
            // SerializeAccessTokenAsync and SerializeIdentityTokenAsync are responsible of ensuring
            // that subsequent access and identity tokens are correctly filtered.

            // Create a new ticket containing the updated properties.
            var ticket = new AuthenticationTicket(identity, properties);

            ticket.Properties.IssuedUtc   = Options.SystemClock.UtcNow;
            ticket.Properties.ExpiresUtc  = ticket.Properties.IssuedUtc;
            ticket.Properties.ExpiresUtc += ticket.GetAuthorizationCodeLifetime() ?? Options.AuthorizationCodeLifetime;

            // Associate a random identifier with the authorization code.
            ticket.SetTokenId(Guid.NewGuid().ToString());

            // Store the code_challenge, code_challenge_method and nonce parameters for later comparison.
            ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallenge, request.CodeChallenge)
            .SetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod, request.CodeChallengeMethod)
            .SetProperty(OpenIdConnectConstants.Properties.Nonce, request.Nonce);

            // Store the original redirect_uri sent by the client application for later comparison.
            ticket.SetProperty(OpenIdConnectConstants.Properties.OriginalRedirectUri,
                               request.GetProperty <string>(OpenIdConnectConstants.Properties.OriginalRedirectUri));

            // Remove the unwanted properties from the authentication ticket.
            ticket.RemoveProperty(OpenIdConnectConstants.Properties.AuthorizationCodeLifetime)
            .RemoveProperty(OpenIdConnectConstants.Properties.TokenUsage);

            var notification = new SerializeAuthorizationCodeContext(Context, Options, request, response, ticket)
            {
                DataFormat = Options.AuthorizationCodeFormat
            };

            await Options.Provider.SerializeAuthorizationCode(notification);

            if (notification.IsHandled || !string.IsNullOrEmpty(notification.AuthorizationCode))
            {
                return(notification.AuthorizationCode);
            }

            if (notification.DataFormat == null)
            {
                throw new InvalidOperationException("A data formatter must be provided.");
            }

            var result = notification.DataFormat.Protect(ticket);

            Logger.LogTrace("A new authorization code was successfully generated using " +
                            "the specified data format: {Code} ; {Claims} ; {Properties}.",
                            result, ticket.Identity.Claims, ticket.Properties.Dictionary);

            return(result);
        }
Ejemplo n.º 3
0
        private async Task CreateAuthorizationAsync([NotNull] AuthenticationTicket ticket,
                                                    [NotNull] OpenIddictServerOptions options, [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 = request.GetProperty($"{OpenIddictConstants.Properties.Application}:{request.ClientId}");
                Debug.Assert(application != null, "The client application shouldn't be null.");

                descriptor.ApplicationId = await _applicationManager.GetIdAsync(application);
            }

            var authorization = await _authorizationManager.CreateAsync(descriptor);

            if (authorization != null)
            {
                var identifier = await _authorizationManager.GetIdAsync(authorization);

                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.InternalAuthorizationId, identifier);
            }
        }
Ejemplo n.º 4
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);
        }
Ejemplo n.º 5
0
        private async Task <AuthenticationTicket> ReceiveTokenAsync(
            [NotNull] string type, [NotNull] string value,
            [NotNull] OpenIddictServerOptions options,
            [NotNull] OpenIdConnectRequest request,
            [NotNull] ISecureDataFormat <AuthenticationTicket> format)
        {
            Debug.Assert(!(options.DisableTokenStorage && 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 validated using this method.");

            string identifier;
            AuthenticationTicket ticket;
            object token;

            if (options.UseReferenceTokens)
            {
                // For introspection or revocation requests, this method may be called more than once.
                // For reference tokens, this may result in multiple database calls being made.
                // To optimize that, the token is added to the request properties to indicate that
                // a database lookup was already made with the same identifier. If the marker exists,
                // the property value (that may be null) is used instead of making a database call.
                if (request.HasProperty($"{OpenIddictConstants.Properties.ReferenceToken}:{value}"))
                {
                    token = request.GetProperty($"{OpenIddictConstants.Properties.ReferenceToken}:{value}");
                }

                else
                {
                    // Retrieve the token entry from the database. If it
                    // cannot be found, assume the token is not valid.
                    token = await _tokenManager.FindByReferenceIdAsync(value);

                    // Store the token as a request property so it can be retrieved if this method is called another time.
                    request.AddProperty($"{OpenIddictConstants.Properties.ReferenceToken}:{value}", token);
                }

                if (token == null)
                {
                    _logger.LogInformation("The reference token corresponding to the '{Identifier}' " +
                                           "reference identifier cannot be found in the database.", value);

                    return(null);
                }

                identifier = await _tokenManager.GetIdAsync(token);

                if (string.IsNullOrEmpty(identifier))
                {
                    _logger.LogWarning("The identifier associated with the received token cannot be retrieved. " +
                                       "This may indicate that the token entry is corrupted.");

                    return(null);
                }

                // Extract the encrypted payload from the token. If it's null or empty,
                // assume the token is not a reference token and consider it as invalid.
                var payload = await _tokenManager.GetPayloadAsync(token);

                if (string.IsNullOrEmpty(payload))
                {
                    _logger.LogWarning("The ciphertext associated with the token '{Identifier}' cannot be retrieved. " +
                                       "This may indicate that the token is not a reference token.", identifier);

                    return(null);
                }

                ticket = format.Unprotect(payload);
                if (ticket == null)
                {
                    _logger.LogWarning("The ciphertext associated with the token '{Identifier}' cannot be decrypted. " +
                                       "This may indicate that the token entry is corrupted or tampered.",
                                       await _tokenManager.GetIdAsync(token));

                    return(null);
                }

                request.SetProperty($"{OpenIddictConstants.Properties.Token}:{identifier}", token);
            }

            else if (type == OpenIdConnectConstants.TokenUsages.AuthorizationCode ||
                     type == OpenIdConnectConstants.TokenUsages.RefreshToken)
            {
                ticket = format.Unprotect(value);
                if (ticket == null)
                {
                    _logger.LogTrace("The received token was invalid or malformed: {Token}.", value);

                    return(null);
                }

                identifier = ticket.GetProperty(OpenIddictConstants.Properties.InternalTokenId);
                if (string.IsNullOrEmpty(identifier))
                {
                    _logger.LogWarning("The identifier associated with the received token cannot be retrieved. " +
                                       "This may indicate that the token entry is corrupted.");

                    return(null);
                }

                // For introspection or revocation requests, this method may be called more than once.
                // For codes/refresh tokens, this may result in multiple database calls being made.
                // To optimize that, the token is added to the request properties to indicate that
                // a database lookup was already made with the same identifier. If the marker exists,
                // the property value (that may be null) is used instead of making a database call.
                if (request.HasProperty($"{OpenIddictConstants.Properties.Token}:{identifier}"))
                {
                    token = request.GetProperty($"{OpenIddictConstants.Properties.Token}:{identifier}");
                }

                // Otherwise, retrieve the authorization code/refresh token entry from the database.
                // If it cannot be found, assume the authorization code/refresh token is not valid.
                else
                {
                    token = await _tokenManager.FindByIdAsync(identifier);

                    // Store the token as a request property so it can be retrieved if this method is called another time.
                    request.AddProperty($"{OpenIddictConstants.Properties.Token}:{identifier}", token);
                }

                if (token == null)
                {
                    _logger.LogInformation("The token '{Identifier}' cannot be found in the database.", identifier);

                    return(null);
                }
            }

            else
            {
                return(null);
            }

            // Dynamically set the creation and expiration dates.
            ticket.Properties.IssuedUtc = await _tokenManager.GetCreationDateAsync(token);

            ticket.Properties.ExpiresUtc = await _tokenManager.GetExpirationDateAsync(token);

            // Restore the token/authorization identifiers using the identifiers attached with the database entry.
            ticket.SetProperty(OpenIddictConstants.Properties.InternalTokenId, identifier);
            ticket.SetProperty(OpenIddictConstants.Properties.InternalAuthorizationId,
                               await _tokenManager.GetAuthorizationIdAsync(token));

            _logger.LogTrace("The token '{Identifier}' was successfully decrypted and " +
                             "retrieved from the database: {Claims} ; {Properties}.",
                             identifier, ticket.Principal.Claims, ticket.Properties.Items);

            return(ticket);
        }
        private async Task <string> CreateTokenAsync(
            [NotNull] string type, [NotNull] AuthenticationTicket ticket,
            [NotNull] KeystoneServerOptions options,
            [NotNull] OpenIdConnectRequest request,
            [NotNull] ISecureDataFormat <AuthenticationTicket> format)
        {
            Debug.Assert(!(options.DisableTokenStorage && options.UseReferenceTokens),
                         "Token storage 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>(
                    KeystoneConstants.Properties.AuthenticationTicket)?.Properties;
                Debug.Assert(properties != null, "The authentication properties shouldn't be null.");

                ticket.Properties.ExpiresUtc = properties.ExpiresUtc;
            }

            if (options.DisableTokenStorage)
            {
                return(null);
            }

            var descriptor = new KeystoneTokenDescriptor
            {
                AuthorizationId = ticket.GetInternalAuthorizationId(),
                CreationDate    = ticket.Properties.IssuedUtc,
                ExpirationDate  = ticket.Properties.ExpiresUtc,
                Principal       = ticket.Principal,
                Status          = KeystoneConstants.Statuses.Valid,
                Subject         = ticket.Principal.GetClaim(KeystoneConstants.Claims.Subject),
                Type            = type
            };

            foreach (var property in ticket.Properties.Items)
            {
                descriptor.Properties.Add(property);
            }

            // 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(KeystoneConstants.Properties.InternalAuthorizationId)
                .RemoveProperty(KeystoneConstants.Properties.InternalTokenId);
            }

            // 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.Payload = 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);

                // Note: the default token manager automatically obfuscates the
                // reference identifier so it can be safely stored in the databse.
                descriptor.ReferenceId = Base64UrlEncoder.Encode(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 _applicationManager.FindByClientIdAsync(request.ClientId);

                if (application == null)
                {
                    throw new InvalidOperationException("The application entry cannot be found in the database.");
                }

                descriptor.ApplicationId = await _applicationManager.GetIdAsync(application);
            }

            // If a null value was returned by CreateAsync(), return immediately.

            // Note: the request cancellation token is deliberately not used here to ensure the caller
            // cannot prevent this operation from being executed by resetting the TCP connection.
            var token = await _tokenManager.CreateAsync(descriptor);

            if (token == null)
            {
                return(null);
            }

            // Throw an exception if the token identifier can't be resolved.
            var identifier = await _tokenManager.GetIdAsync(token);

            if (string.IsNullOrEmpty(identifier))
            {
                throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty.");
            }

            // Dynamically set the creation and expiration dates.
            ticket.Properties.IssuedUtc  = descriptor.CreationDate;
            ticket.Properties.ExpiresUtc = descriptor.ExpirationDate;

            // Restore the token/authorization identifiers using the identifiers attached with the database entry.
            ticket.SetInternalAuthorizationId(descriptor.AuthorizationId)
            .SetInternalTokenId(identifier);

            if (options.UseReferenceTokens)
            {
                _logger.LogTrace("A new reference token was successfully generated and persisted " +
                                 "in the database: {Token} ; {Claims} ; {Properties}.",
                                 descriptor.ReferenceId, ticket.Principal.Claims, ticket.Properties.Items);

                return(descriptor.ReferenceId);
            }

            return(null);
        }