private async Task <string> SerializeIdentityTokenAsync(
            ClaimsIdentity identity, AuthenticationProperties properties,
            OpenIdConnectRequest request, OpenIdConnectResponse response)
        {
            // Replace the identity by a new one containing only the filtered claims.
            // Actors identities are also filtered (delegation scenarios).
            identity = identity.Clone(claim =>
            {
                // Never exclude the subject claim.
                if (string.Equals(claim.Type, OpenIdConnectConstants.Claims.Subject, StringComparison.OrdinalIgnoreCase))
                {
                    return(true);
                }

                // Claims whose destination is not explicitly referenced or doesn't
                // contain "id_token" are not included in the identity token.
                if (!claim.HasDestination(OpenIdConnectConstants.Destinations.IdentityToken))
                {
                    Logger.LogDebug("'{Claim}' was excluded from the identity token claims.", claim.Type);

                    return(false);
                }

                return(true);
            });

            // Remove the destinations from the claim properties.
            foreach (var claim in identity.Claims)
            {
                claim.Properties.Remove(OpenIdConnectConstants.Properties.Destinations);
            }

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

            ticket.Properties.IssuedUtc = Options.SystemClock.UtcNow;

            // Only set the expiration date if a lifetime was specified in either the ticket or the options.
            var lifetime = ticket.GetIdentityTokenLifetime() ?? Options.IdentityTokenLifetime;

            if (lifetime.HasValue)
            {
                ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc + lifetime.Value;
            }

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

            // Remove the unwanted properties from the authentication ticket.
            ticket.RemoveProperty(OpenIdConnectConstants.Properties.AccessTokenLifetime)
            .RemoveProperty(OpenIdConnectConstants.Properties.AuthorizationCodeLifetime)
            .RemoveProperty(OpenIdConnectConstants.Properties.CodeChallenge)
            .RemoveProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod)
            .RemoveProperty(OpenIdConnectConstants.Properties.IdentityTokenLifetime)
            .RemoveProperty(OpenIdConnectConstants.Properties.OriginalRedirectUri)
            .RemoveProperty(OpenIdConnectConstants.Properties.RefreshTokenLifetime)
            .RemoveProperty(OpenIdConnectConstants.Properties.TokenUsage);

            ticket.SetAudiences(ticket.GetPresenters());

            var notification = new SerializeIdentityTokenContext(Context, Options, request, response, ticket)
            {
                Issuer = Context.GetIssuer(Options),
                SecurityTokenHandler = Options.IdentityTokenHandler,
                SigningCredentials   = Options.SigningCredentials.FirstOrDefault(
                    credentials => credentials.Key is AsymmetricSecurityKey)
            };

            await Options.Provider.SerializeIdentityToken(notification);

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

            if (notification.SecurityTokenHandler == null)
            {
                throw new InvalidOperationException("A security token handler must be provided.");
            }

            if (string.IsNullOrEmpty(identity.GetClaim(OpenIdConnectConstants.Claims.Subject)))
            {
                throw new InvalidOperationException("The authentication ticket was rejected because " +
                                                    "the mandatory subject claim was missing.");
            }

            // Note: identity tokens must be signed but an exception is made by the OpenID Connect specification
            // when they are returned from the token endpoint: in this case, signing is not mandatory, as the TLS
            // server validation can be used as a way to ensure an identity token was issued by a trusted party.
            // See http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation for more information.
            if (notification.SigningCredentials == null && request.IsAuthorizationRequest())
            {
                throw new InvalidOperationException("A signing key must be provided.");
            }

            // Store the "usage" property as a claim.
            ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.TokenUsage, OpenIdConnectConstants.TokenUsages.IdToken);

            // Store the "unique_id" property as a claim.
            ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.JwtId, ticket.GetTokenId());

            // Store the "confidentiality_level" property as a claim.
            var confidentiality = ticket.GetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel);

            if (!string.IsNullOrEmpty(confidentiality))
            {
                identity.AddClaim(OpenIdConnectConstants.Claims.ConfidentialityLevel, confidentiality);
            }

            // Store the audiences as claims.
            foreach (var audience in notification.Audiences)
            {
                ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Audience, audience);
            }

            // If a nonce was present in the authorization request, it MUST
            // be included in the id_token generated by the token endpoint.
            // See http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
            var nonce = request.Nonce;

            if (request.IsAuthorizationCodeGrantType())
            {
                // Restore the nonce stored in the authentication
                // ticket extracted from the authorization code.
                nonce = ticket.GetProperty(OpenIdConnectConstants.Properties.Nonce);
            }

            if (!string.IsNullOrEmpty(nonce))
            {
                ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Nonce, nonce);
            }

            if (notification.SigningCredentials != null && (!string.IsNullOrEmpty(response.Code) ||
                                                            !string.IsNullOrEmpty(response.AccessToken)))
            {
                using (var algorithm = OpenIdConnectServerHelpers.GetHashAlgorithm(notification.SigningCredentials.Algorithm))
                {
                    // Create an authorization code hash if necessary.
                    if (!string.IsNullOrEmpty(response.Code))
                    {
                        var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(response.Code));

                        // Note: only the left-most half of the hash of the octets is used.
                        // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
                        identity.AddClaim(OpenIdConnectConstants.Claims.CodeHash, Base64UrlEncoder.Encode(hash, 0, hash.Length / 2));
                    }

                    // Create an access token hash if necessary.
                    if (!string.IsNullOrEmpty(response.AccessToken))
                    {
                        var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(response.AccessToken));

                        // Note: only the left-most half of the hash of the octets is used.
                        // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
                        identity.AddClaim(OpenIdConnectConstants.Claims.AccessTokenHash, Base64UrlEncoder.Encode(hash, 0, hash.Length / 2));
                    }
                }
            }

            // Extract the presenters from the authentication ticket.
            var presenters = notification.Presenters.ToArray();

            switch (presenters.Length)
            {
            case 0: break;

            case 1:
                identity.AddClaim(OpenIdConnectConstants.Claims.AuthorizedParty, presenters[0]);
                break;

            default:
                Logger.LogWarning("Multiple presenters have been associated with the identity token " +
                                  "but the JWT format only accepts single values.");

                // Only add the first authorized party.
                identity.AddClaim(OpenIdConnectConstants.Claims.AuthorizedParty, presenters[0]);
                break;
            }

            var token = notification.SecurityTokenHandler.CreateEncodedJwt(new SecurityTokenDescriptor
            {
                Subject = identity,
                Issuer  = notification.Issuer,
                EncryptingCredentials = notification.EncryptingCredentials,
                SigningCredentials    = notification.SigningCredentials,
                IssuedAt  = notification.Ticket.Properties.IssuedUtc?.UtcDateTime,
                NotBefore = notification.Ticket.Properties.IssuedUtc?.UtcDateTime,
                Expires   = notification.Ticket.Properties.ExpiresUtc?.UtcDateTime
            });

            Logger.LogTrace("A new identity token was successfully generated using the specified " +
                            "security token handler: {Token} ; {Claims} ; {Properties}.",
                            token, ticket.Identity.Claims, ticket.Properties.Dictionary);

            return(token);
        }