private async Task <string> SerializeIdentityTokenAsync(
            ClaimsPrincipal principal, AuthenticationProperties properties,
            OpenIdConnectMessage request, OpenIdConnectMessage response)
        {
            // properties.IssuedUtc and properties.ExpiresUtc
            // should always be preferred when explicitly set.
            if (properties.IssuedUtc == null)
            {
                properties.IssuedUtc = Options.SystemClock.UtcNow;
            }

            if (properties.ExpiresUtc == null)
            {
                properties.ExpiresUtc = properties.IssuedUtc + Options.IdentityTokenLifetime;
            }

            // Replace the principal by a new one containing only the filtered claims.
            // Actors identities are also filtered (delegation scenarios).
            principal = principal.Clone(claim => {
                // Never exclude ClaimTypes.NameIdentifier.
                if (string.Equals(claim.Type, ClaimTypes.NameIdentifier, StringComparison.OrdinalIgnoreCase))
                {
                    return(true);
                }

                // Claims whose destination is not explicitly referenced or doesn't
                // contain "id_token" are not included in the identity token.
                return(claim.HasDestination(OpenIdConnectConstants.Destinations.IdentityToken));
            });

            var identity = (ClaimsIdentity)principal.Identity;

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

            ticket.SetUsage(OpenIdConnectConstants.Usages.IdToken);

            // By default, add the client_id to the list of the
            // presenters allowed to use the identity token.
            if (!string.IsNullOrEmpty(request.ClientId))
            {
                ticket.SetAudiences(request.ClientId);
                ticket.SetPresenters(request.ClientId);
            }

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

            await Options.Provider.SerializeIdentityToken(notification);

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

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

            if (!identity.HasClaim(claim => claim.Type == JwtRegisteredClaimNames.Sub) &&
                !identity.HasClaim(claim => claim.Type == ClaimTypes.NameIdentifier))
            {
                Logger.LogError("A unique identifier cannot be found to generate a 'sub' claim: " +
                                "make sure to add a 'ClaimTypes.NameIdentifier' claim.");

                return(null);
            }

            // Extract the main identity from the principal.
            identity = (ClaimsIdentity)ticket.Principal.Identity;

            // Store the unique subject identifier as a claim.
            if (!identity.HasClaim(claim => claim.Type == JwtRegisteredClaimNames.Sub))
            {
                identity.AddClaim(JwtRegisteredClaimNames.Sub, identity.GetClaim(ClaimTypes.NameIdentifier));
            }

            // Remove the ClaimTypes.NameIdentifier claims to avoid getting duplicate claims.
            // Note: the "sub" claim is automatically mapped by JwtSecurityTokenHandler
            // to ClaimTypes.NameIdentifier when validating a JWT token.
            // Note: make sure to call ToArray() to avoid an InvalidOperationException
            // on old versions of Mono, where FindAll() is implemented using an iterator.
            foreach (var claim in identity.FindAll(ClaimTypes.NameIdentifier).ToArray())
            {
                identity.RemoveClaim(claim);
            }

            // Store the "usage" property as a claim.
            identity.AddClaim(OpenIdConnectConstants.Claims.Usage, ticket.GetUsage());

            // If the ticket is marked as confidential, add a new
            // "confidential" claim in the security token.
            if (ticket.IsConfidential())
            {
                identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Confidential, "true", ClaimValueTypes.Boolean));
            }

            // Store the audiences as claims.
            foreach (var audience in ticket.GetAudiences())
            {
                identity.AddClaim(JwtRegisteredClaimNames.Aud, 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.GetNonce();
            }

            if (!string.IsNullOrEmpty(nonce))
            {
                identity.AddClaim(JwtRegisteredClaimNames.Nonce, nonce);
            }

            if (!string.IsNullOrEmpty(response.Code))
            {
                using (var algorithm = OpenIdConnectServerHelpers.GetHashAlgorithm(notification.SigningCredentials.Algorithm)) {
                    // Create the c_hash using the authorization code returned by SerializeAuthorizationCodeAsync.
                    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(JwtRegisteredClaimNames.CHash, Base64UrlEncoder.Encode(hash, 0, hash.Length / 2));
                }
            }

            if (!string.IsNullOrEmpty(response.AccessToken))
            {
                using (var algorithm = OpenIdConnectServerHelpers.GetHashAlgorithm(notification.SigningCredentials.Algorithm)) {
                    // Create the at_hash using the access token returned by SerializeAccessTokenAsync.
                    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(JwtRegisteredClaimNames.AtHash, Base64UrlEncoder.Encode(hash, 0, hash.Length / 2));
                }
            }

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

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

            case 1:
                identity.AddClaim(JwtRegisteredClaimNames.Azp, 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(JwtRegisteredClaimNames.Azp, presenters[0]);
                break;
            }

            var token = notification.SecurityTokenHandler.CreateJwtSecurityToken(
                subject: identity,
                issuer: notification.Issuer,
                signingCredentials: notification.SigningCredentials,
                issuedAt: ticket.Properties.IssuedUtc.Value.UtcDateTime,
                notBefore: ticket.Properties.IssuedUtc.Value.UtcDateTime,
                expires: ticket.Properties.ExpiresUtc.Value.UtcDateTime);

            var x509SecurityKey = notification.SigningCredentials?.Key as X509SecurityKey;

            if (x509SecurityKey != null)
            {
                // Note: unlike "kid", "x5t" is not automatically added by JwtHeader's constructor in IdentityModel for .NET Core.
                // Though not required by the specifications, this property is needed for IdentityModel for Katana to work correctly.
                // See https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/issues/132
                // and https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/181.
                token.Header[JwtHeaderParameterNames.X5t] = Base64UrlEncoder.Encode(x509SecurityKey.Certificate.GetCertHash());
            }

            return(notification.SecurityTokenHandler.WriteToken(token));
        }
        private async Task <string> SerializeIdentityTokenAsync(
            ClaimsPrincipal principal, AuthenticationProperties properties,
            OpenIdConnectMessage request, OpenIdConnectMessage response)
        {
            // properties.IssuedUtc and properties.ExpiresUtc
            // should always be preferred when explicitly set.
            if (properties.IssuedUtc == null)
            {
                properties.IssuedUtc = Options.SystemClock.UtcNow;
            }

            if (properties.ExpiresUtc == null)
            {
                properties.ExpiresUtc = properties.IssuedUtc + Options.IdentityTokenLifetime;
            }

            properties.SetUsage(OpenIdConnectConstants.Usages.IdToken);

            // Replace the principal by a new one containing only the filtered claims.
            // Actors identities are also filtered (delegation scenarios).
            principal = principal.Clone(claim => {
                // ClaimTypes.NameIdentifier and JwtRegisteredClaimNames.Sub are never excluded.
                if (string.Equals(claim.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal) ||
                    string.Equals(claim.Type, JwtRegisteredClaimNames.Sub, StringComparison.Ordinal))
                {
                    return(true);
                }

                // Claims whose destination is not explicitly referenced or
                // doesn't contain "id_token" are not included in the identity token.
                return(claim.HasDestination(OpenIdConnectConstants.ResponseTypes.IdToken));
            });

            var identity = (ClaimsIdentity)principal.Identity;

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

            var notification = new SerializeIdentityTokenContext(Context, Options, request, response, ticket)
            {
                Confidential         = ticket.IsConfidential(),
                Issuer               = Context.GetIssuer(Options),
                Nonce                = request.Nonce,
                SecurityTokenHandler = Options.IdentityTokenHandler,
                SigningCredentials   = Options.SigningCredentials.FirstOrDefault()
            };

            // 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
            if (request.IsAuthorizationCodeGrantType())
            {
                // Restore the nonce stored in the authentication
                // ticket extracted from the authorization code.
                notification.Nonce = ticket.GetNonce();
            }

            // While the 'sub' claim is declared mandatory by the OIDC specs,
            // it is not always issued as-is by the authorization servers.
            // When missing, the name identifier claim is used as a substitute.
            // See http://openid.net/specs/openid-connect-core-1_0.html#IDToken
            notification.Subject = identity.GetClaim(JwtRegisteredClaimNames.Sub) ??
                                   identity.GetClaim(ClaimTypes.NameIdentifier);

            // Only add client_id in the audiences list if it is non-null.
            if (!string.IsNullOrEmpty(request.ClientId))
            {
                notification.Audiences.Add(request.ClientId);
            }

            if (!string.IsNullOrEmpty(response.Code))
            {
                using (var algorithm = SHA256.Create()) {
                    // Create the c_hash using the authorization code returned by SerializeAuthorizationCodeAsync.
                    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
                    notification.CHash = Base64UrlEncoder.Encode(hash, 0, hash.Length / 2);
                }
            }

            if (!string.IsNullOrEmpty(response.AccessToken))
            {
                using (var algorithm = SHA256.Create()) {
                    // Create the at_hash using the access token returned by SerializeAccessTokenAsync.
                    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
                    notification.AtHash = Base64UrlEncoder.Encode(hash, 0, hash.Length / 2);
                }
            }

            await Options.Provider.SerializeIdentityToken(notification);

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

            // Allow the application to change the authentication
            // ticket from the SerializeIdentityTokenAsync event.
            ticket = notification.AuthenticationTicket;
            ticket.Properties.CopyTo(properties);

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

            if (string.IsNullOrEmpty(notification.Subject))
            {
                Logger.LogError("A unique identifier cannot be found to generate a 'sub' claim. " +
                                "Make sure to either add a 'sub' or a 'ClaimTypes.NameIdentifier' claim " +
                                "in the returned ClaimsIdentity before calling SignIn.");

                return(null);
            }

            // Extract the main identity from the principal.
            identity = (ClaimsIdentity)ticket.Principal.Identity;

            // Remove the ClaimTypes.NameIdentifier claims to avoid getting duplicate claims.
            // Note: the "sub" claim is automatically mapped by JwtSecurityTokenHandler
            // to ClaimTypes.NameIdentifier when validating a JWT token.
            // Note: make sure to call ToArray() to avoid an InvalidOperationException
            // on old versions of Mono, where FindAll() is implemented using an iterator.
            foreach (var claim in identity.FindAll(ClaimTypes.NameIdentifier).ToArray())
            {
                identity.RemoveClaim(claim);
            }

            // Store the unique subject identifier as a claim.
            identity.AddClaim(JwtRegisteredClaimNames.Sub, notification.Subject);

            // Store the "usage" property as a claim.
            identity.AddClaim(OpenIdConnectConstants.Extra.Usage, ticket.Properties.GetUsage());

            // Store the audiences as claims.
            foreach (var audience in notification.Audiences)
            {
                identity.AddClaim(JwtRegisteredClaimNames.Aud, audience);
            }

            if (!string.IsNullOrEmpty(notification.AtHash))
            {
                identity.AddClaim(JwtRegisteredClaimNames.AtHash, notification.AtHash);
            }

            if (!string.IsNullOrEmpty(notification.CHash))
            {
                identity.AddClaim(JwtRegisteredClaimNames.CHash, notification.CHash);
            }

            if (!string.IsNullOrEmpty(notification.Nonce))
            {
                identity.AddClaim(JwtRegisteredClaimNames.Nonce, notification.Nonce);
            }

            // If the ticket is marked as confidential, add a new
            // "confidential" claim in the security token.
            if (notification.Confidential)
            {
                identity.AddClaim(new Claim(OpenIdConnectConstants.Extra.Confidential, "true", ClaimValueTypes.Boolean));
            }

            var token = notification.SecurityTokenHandler.CreateToken(
                subject: identity,
                issuer: notification.Issuer,
                signingCredentials: notification.SigningCredentials,
                issuedAt: ticket.Properties.IssuedUtc.Value.UtcDateTime,
                notBefore: ticket.Properties.IssuedUtc.Value.UtcDateTime,
                expires: ticket.Properties.ExpiresUtc.Value.UtcDateTime);

            if (notification.SigningCredentials != null)
            {
                var x509SecurityKey = notification.SigningCredentials.Key as X509SecurityKey;
                if (x509SecurityKey != null)
                {
                    // Note: unlike "kid", "x5t" is not automatically added by JwtHeader's constructor in IdentityModel for ASP.NET 5.
                    // Though not required by the specifications, this property is needed for IdentityModel for Katana to work correctly.
                    // See https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/issues/132
                    // and https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/181.
                    token.Header[JwtHeaderParameterNames.X5t] = Base64UrlEncoder.Encode(x509SecurityKey.Certificate.GetCertHash());
                }

                object identifier;
                if (!token.Header.TryGetValue(JwtHeaderParameterNames.Kid, out identifier) || identifier == null)
                {
                    // When the token doesn't contain a "kid" parameter in the header, automatically
                    // add one using the identifier specified in the signing credentials.
                    identifier = notification.SigningCredentials.Kid;

                    if (identifier == null)
                    {
                        // When no key identifier has been explicitly added by the developer, a "kid" is automatically
                        // inferred from the hexadecimal representation of the certificate thumbprint (SHA-1).
                        if (x509SecurityKey != null)
                        {
                            identifier = x509SecurityKey.Certificate.Thumbprint;
                        }

                        // When no key identifier has been explicitly added by the developer, a "kid"
                        // is automatically inferred from the modulus if the signing key is a RSA key.
                        var rsaSecurityKey = notification.SigningCredentials.Key as RsaSecurityKey;
                        if (rsaSecurityKey != null)
                        {
                            // Only use the 40 first chars to match the identifier used by the JWKS endpoint.
                            identifier = Base64UrlEncoder.Encode(rsaSecurityKey.Parameters.Modulus)
                                         .Substring(0, 40)
                                         .ToUpperInvariant();
                        }
                    }

                    token.Header[JwtHeaderParameterNames.Kid] = identifier;
                }
            }

            return(notification.SecurityTokenHandler.WriteToken(token));
        }
        private async Task <string> SerializeIdentityTokenAsync(
            ClaimsIdentity identity, AuthenticationProperties properties,
            OpenIdConnectMessage request, OpenIdConnectMessage response)
        {
            // properties.IssuedUtc and properties.ExpiresUtc
            // should always be preferred when explicitly set.
            if (properties.IssuedUtc == null)
            {
                properties.IssuedUtc = Options.SystemClock.UtcNow;
            }

            if (properties.ExpiresUtc == null)
            {
                properties.ExpiresUtc = properties.IssuedUtc + Options.IdentityTokenLifetime;
            }

            // 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 ClaimTypes.NameIdentifier.
                if (string.Equals(claim.Type, ClaimTypes.NameIdentifier, StringComparison.OrdinalIgnoreCase))
                {
                    return(true);
                }

                // Claims whose destination is not explicitly referenced or doesn't
                // contain "id_token" are not included in the identity token.
                return(claim.HasDestination(OpenIdConnectConstants.Destinations.IdentityToken));
            });

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

            ticket.SetUsage(OpenIdConnectConstants.Usages.IdToken);

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

            // By default, add the client_id to the list of the
            // presenters allowed to use the identity token.
            if (!string.IsNullOrEmpty(request.ClientId))
            {
                ticket.SetAudiences(request.ClientId);
                ticket.SetPresenters(request.ClientId);
            }

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

            await Options.Provider.SerializeIdentityToken(notification);

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

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

            if (!identity.HasClaim(claim => claim.Type == OpenIdConnectConstants.Claims.Subject) &&
                !identity.HasClaim(claim => claim.Type == ClaimTypes.NameIdentifier))
            {
                throw new InvalidOperationException("A unique identifier cannot be found to generate a 'sub' claim: " +
                                                    "make sure to add a 'ClaimTypes.NameIdentifier' claim.");
            }

            // Store the unique subject identifier as a claim.
            if (!identity.HasClaim(claim => claim.Type == OpenIdConnectConstants.Claims.Subject))
            {
                identity.AddClaim(OpenIdConnectConstants.Claims.Subject, identity.GetClaim(ClaimTypes.NameIdentifier));
            }

            // Remove the ClaimTypes.NameIdentifier claims to avoid getting duplicate claims.
            // Note: the "sub" claim is automatically mapped by JwtSecurityTokenHandler
            // to ClaimTypes.NameIdentifier when validating a JWT token.
            // Note: make sure to call ToArray() to avoid an InvalidOperationException
            // on old versions of Mono, where FindAll() is implemented using an iterator.
            foreach (var claim in identity.FindAll(ClaimTypes.NameIdentifier).ToArray())
            {
                identity.RemoveClaim(claim);
            }

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

            // Store the "usage" property as a claim.
            ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Usage, ticket.GetUsage());

            // If the ticket is marked as confidential, add a new
            // "confidential" claim in the security token.
            if (ticket.IsConfidential())
            {
                ticket.Identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Confidential, "true", ClaimValueTypes.Boolean));
            }

            // Store the audiences as claims.
            foreach (var audience in ticket.GetAudiences())
            {
                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.GetNonce();
            }

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

            if (!string.IsNullOrEmpty(response.Code))
            {
                using (var algorithm = HashAlgorithm.Create(notification.SigningCredentials.DigestAlgorithm)) {
                    // Create the c_hash using the authorization code returned by SerializeAuthorizationCodeAsync.
                    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
                    ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.CodeHash,
                                             Base64UrlEncoder.Encode(hash, 0, hash.Length / 2));
                }
            }

            if (!string.IsNullOrEmpty(response.AccessToken))
            {
                using (var algorithm = HashAlgorithm.Create(notification.SigningCredentials.DigestAlgorithm)) {
                    // Create the at_hash using the access token returned by SerializeAccessTokenAsync.
                    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
                    ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.AccessTokenHash,
                                             Base64UrlEncoder.Encode(hash, 0, hash.Length / 2));
                }
            }

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

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

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

            default:
                Options.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.CreateToken(
                subject: ticket.Identity,
                issuer: notification.Issuer,
                signingCredentials: notification.SigningCredentials,
                notBefore: ticket.Properties.IssuedUtc.Value.UtcDateTime,
                expires: ticket.Properties.ExpiresUtc.Value.UtcDateTime);

            token.Payload[OpenIdConnectConstants.Claims.IssuedAt] =
                EpochTime.GetIntDate(ticket.Properties.IssuedUtc.Value.UtcDateTime);

            // Try to extract a key identifier from the signing credentials
            // and add the "kid" property to the JWT header if applicable.
            LocalIdKeyIdentifierClause clause = null;

            if (notification.SigningCredentials?.SigningKeyIdentifier != null &&
                notification.SigningCredentials.SigningKeyIdentifier.TryFind(out clause))
            {
                token.Header[JwtHeaderParameterNames.Kid] = clause.LocalId;
            }

            return(notification.SecurityTokenHandler.WriteToken(token));
        }