Beispiel #1
0
        public void GetTicketId_ReturnsExpectedResult(string identifier)
        {
            // Arrange
            var ticket = new AuthenticationTicket(
                new ClaimsIdentity(),
                new AuthenticationProperties());

            ticket.Properties.Dictionary[OpenIdConnectConstants.Properties.TicketId] = identifier;

            // Act and assert
            Assert.Equal(identifier, ticket.GetTicketId());
        }
        private async Task <string> SerializeAccessTokenAsync(
            ClaimsPrincipal principal, AuthenticationProperties properties,
            OpenIdConnectRequest request, OpenIdConnectResponse response)
        {
            // Create a new principal containing only the filtered claims.
            // Actors identities are also filtered (delegation scenarios).
            principal = principal.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 "access_token" are not included in the access token.
                if (!claim.HasDestination(OpenIdConnectConstants.Destinations.AccessToken))
                {
                    Logger.LogDebug("'{Claim}' was excluded from the access token claims.", claim.Type);

                    return(false);
                }

                return(true);
            });

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

            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.Properties.IssuedUtc  = Options.SystemClock.UtcNow;
            ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc +
                                           (ticket.GetAccessTokenLifetime() ?? Options.AccessTokenLifetime);

            ticket.SetUsage(OpenIdConnectConstants.Usages.AccessToken);
            ticket.SetAudiences(ticket.GetResources());

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

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

            var notification = new SerializeAccessTokenContext(Context, Options, request, response, ticket)
            {
                DataFormat           = Options.AccessTokenFormat,
                Issuer               = Context.GetIssuer(Options),
                SecurityTokenHandler = Options.AccessTokenHandler,
                SigningCredentials   = Options.SigningCredentials.FirstOrDefault(key => key.Key is SymmetricSecurityKey) ??
                                       Options.SigningCredentials.FirstOrDefault()
            };

            await Options.Provider.SerializeAccessToken(notification);

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

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

            if (!ReferenceEquals(ticket, notification.Ticket))
            {
                throw new InvalidOperationException("The authentication ticket cannot be replaced.");
            }

            if (notification.SecurityTokenHandler == null)
            {
                return(notification.DataFormat?.Protect(ticket));
            }

            // At this stage, throw an exception if no signing credentials were provided.
            if (notification.SigningCredentials == null)
            {
                throw new InvalidOperationException("A signing key must be provided.");
            }

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

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

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

            // 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);
            }

            // Create a new claim per scope item, that will result
            // in a "scope" array being added in the access token.
            foreach (var scope in notification.Scopes)
            {
                identity.AddClaim(OpenIdConnectConstants.Claims.Scope, scope);
            }

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

            // 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 access 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(new SecurityTokenDescriptor
            {
                Subject            = identity,
                Issuer             = notification.Issuer,
                SigningCredentials = notification.SigningCredentials,
                IssuedAt           = notification.Ticket.Properties.IssuedUtc?.UtcDateTime,
                NotBefore          = notification.Ticket.Properties.IssuedUtc?.UtcDateTime,
                Expires            = notification.Ticket.Properties.ExpiresUtc?.UtcDateTime
            });

            return(notification.SecurityTokenHandler.WriteToken(token));
        }
        private async Task <string> SerializeIdentityTokenAsync(
            ClaimsPrincipal principal, AuthenticationProperties properties,
            OpenIdConnectRequest request, OpenIdConnectResponse response)
        {
            // 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 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 principal.Claims)
            {
                claim.Properties.Remove(OpenIdConnectConstants.Properties.Destinations);
            }

            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.Properties.IssuedUtc  = Options.SystemClock.UtcNow;
            ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc +
                                           (ticket.GetIdentityTokenLifetime() ?? Options.IdentityTokenLifetime);

            ticket.SetUsage(OpenIdConnectConstants.Usages.IdentityToken);

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

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

            ticket.SetAudiences(ticket.GetPresenters());

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

            await Options.Provider.SerializeIdentityToken(notification);

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

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

            if (!ReferenceEquals(ticket, notification.Ticket))
            {
                throw new InvalidOperationException("The authentication ticket cannot be replaced.");
            }

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

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

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

            // 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 "unique_id" property as a claim.
            identity.AddClaim(OpenIdConnectConstants.Claims.JwtId, ticket.GetTicketId());

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

            // 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)
            {
                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))
            {
                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.CreateToken(new SecurityTokenDescriptor
            {
                Subject            = identity,
                Issuer             = notification.Issuer,
                SigningCredentials = notification.SigningCredentials,
                IssuedAt           = notification.Ticket.Properties.IssuedUtc?.UtcDateTime,
                NotBefore          = notification.Ticket.Properties.IssuedUtc?.UtcDateTime,
                Expires            = notification.Ticket.Properties.ExpiresUtc?.UtcDateTime
            });

            return(notification.SecurityTokenHandler.WriteToken(token));
        }
Beispiel #4
0
        private async Task <bool> InvokeIntrospectionEndpointAsync()
        {
            OpenIdConnectMessage request;

            // See https://tools.ietf.org/html/rfc7662#section-2.1
            // and https://tools.ietf.org/html/rfc7662#section-4
            if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                request = new OpenIdConnectMessage(Request.Query)
                {
                    RequestType = OpenIdConnectRequestType.AuthenticationRequest
                };
            }

            else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
            {
                // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization
                if (string.IsNullOrEmpty(Request.ContentType))
                {
                    Options.Logger.LogError("The introspection request was rejected because " +
                                            "the mandatory 'Content-Type' header was missing.");

                    return(await SendIntrospectionResponseAsync(null, new OpenIdConnectMessage {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed introspection request has been received: " +
                                           "the mandatory 'Content-Type' header was missing from the POST request."
                    }));
                }

                // May have media/type; charset=utf-8, allow partial match.
                if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase))
                {
                    Options.Logger.LogError("The introspection request was rejected because an invalid 'Content-Type' " +
                                            "header was received: {ContentType}.", Request.ContentType);

                    return(await SendIntrospectionResponseAsync(null, new OpenIdConnectMessage {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed introspection request has been received: " +
                                           "the 'Content-Type' header contained an unexcepted value. " +
                                           "Make sure to use 'application/x-www-form-urlencoded'."
                    }));
                }

                request = new OpenIdConnectMessage(await Request.ReadFormAsync())
                {
                    RequestType = OpenIdConnectRequestType.AuthenticationRequest
                };
            }

            else
            {
                Options.Logger.LogError("The introspection request was rejected because an invalid " +
                                        "HTTP method was received: {Method}.", Request.Method);

                return(await SendIntrospectionResponseAsync(null, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "A malformed introspection request has been received: " +
                                       "make sure to use either GET or POST."
                }));
            }

            if (string.IsNullOrWhiteSpace(request.Token))
            {
                return(await SendIntrospectionResponseAsync(request, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "A malformed introspection request has been received: " +
                                       "a 'token' parameter with an access, refresh, or identity token is required."
                }));
            }

            // Insert the introspection request in the OWIN context.
            Context.SetOpenIdConnectRequest(request);

            // When client_id and client_secret are both null, try to extract them from the Authorization header.
            // See http://tools.ietf.org/html/rfc6749#section-2.3.1 and
            // http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
            if (string.IsNullOrEmpty(request.ClientId) && string.IsNullOrEmpty(request.ClientSecret))
            {
                var header = Request.Headers.Get("Authorization");
                if (!string.IsNullOrEmpty(header) && header.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
                {
                    try {
                        var value = header.Substring("Basic ".Length).Trim();
                        var data  = Encoding.UTF8.GetString(Convert.FromBase64String(value));

                        var index = data.IndexOf(':');
                        if (index >= 0)
                        {
                            request.ClientId     = data.Substring(0, index);
                            request.ClientSecret = data.Substring(index + 1);
                        }
                    }

                    catch (FormatException) { }
                    catch (ArgumentException) { }
                }
            }

            var context = new ValidateIntrospectionRequestContext(Context, Options, request);
            await Options.Provider.ValidateIntrospectionRequest(context);

            if (context.IsRejected)
            {
                Options.Logger.LogInformation("The introspection request was rejected by application code.");

                return(await SendIntrospectionResponseAsync(request, new OpenIdConnectMessage {
                    Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = context.ErrorDescription,
                    ErrorUri = context.ErrorUri
                }));
            }

            // Ensure that the client_id has been set from the ValidateIntrospectionRequest event.
            else if (context.IsValidated && string.IsNullOrEmpty(request.ClientId))
            {
                Options.Logger.LogError("The introspection request was validated but the client_id was not set.");

                return(await SendIntrospectionResponseAsync(request, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.ServerError,
                    ErrorDescription = "An internal server error occurred."
                }));
            }

            AuthenticationTicket ticket = null;

            // Note: use the "token_type_hint" parameter to determine
            // the type of the token sent by the client application.
            // See https://tools.ietf.org/html/rfc7662#section-2.1
            switch (request.GetTokenTypeHint())
            {
            case OpenIdConnectConstants.Usages.AccessToken:
                ticket = await DeserializeAccessTokenAsync(request.Token, request);

                break;

            case OpenIdConnectConstants.Usages.RefreshToken:
                ticket = await DeserializeRefreshTokenAsync(request.Token, request);

                break;

            case OpenIdConnectConstants.Usages.IdToken:
                ticket = await DeserializeIdentityTokenAsync(request.Token, request);

                break;
            }

            // Note: if the token can't be found using "token_type_hint",
            // the search must be extended to all supported token types.
            // See https://tools.ietf.org/html/rfc7662#section-2.1
            if (ticket == null)
            {
                ticket = await DeserializeAccessTokenAsync(request.Token, request) ??
                         await DeserializeIdentityTokenAsync(request.Token, request) ??
                         await DeserializeRefreshTokenAsync(request.Token, request);
            }

            if (ticket == null)
            {
                Options.Logger.LogInformation("The introspection request was rejected because the token was invalid.");

                return(await SendIntrospectionResponseAsync(request, new JObject {
                    [OpenIdConnectConstants.Claims.Active] = false
                }));
            }

            // Note: unlike refresh or identity tokens that can only be validated by client applications,
            // access tokens can be validated by either resource servers or client applications:
            // in both cases, the caller must be authenticated if the ticket is marked as confidential.
            if (context.IsSkipped && ticket.IsConfidential())
            {
                Options.Logger.LogWarning("The introspection request was rejected because the caller was not authenticated.");

                return(await SendIntrospectionResponseAsync(request, new JObject {
                    [OpenIdConnectConstants.Claims.Active] = false
                }));
            }

            // If the ticket is already expired, directly return active=false.
            if (ticket.Properties.ExpiresUtc.HasValue &&
                ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow)
            {
                Options.Logger.LogInformation("The introspection request was rejected because the token was expired.");

                return(await SendIntrospectionResponseAsync(request, new JObject {
                    [OpenIdConnectConstants.Claims.Active] = false
                }));
            }

            // When a client_id can be inferred from the introspection request,
            // ensure that the client application is a valid audience/presenter.
            if (!string.IsNullOrEmpty(request.ClientId))
            {
                // Ensure the caller is listed as a valid audience or authorized presenter.
                if (ticket.IsAccessToken() && ticket.HasAudience() && !ticket.HasAudience(request.ClientId) &&
                    ticket.HasPresenter() && !ticket.HasPresenter(request.ClientId))
                {
                    Options.Logger.LogWarning("The introspection request was rejected because the access token " +
                                              "was issued to a different client or for another resource server.");

                    return(await SendIntrospectionResponseAsync(request, new JObject {
                        [OpenIdConnectConstants.Claims.Active] = false
                    }));
                }

                // Reject the request if the caller is not listed as a valid audience.
                else if (ticket.IsIdentityToken() && ticket.HasAudience() && !ticket.HasAudience(request.ClientId))
                {
                    Options.Logger.LogWarning("The introspection request was rejected because the " +
                                              "identity token was issued to a different client.");

                    return(await SendIntrospectionResponseAsync(request, new JObject {
                        [OpenIdConnectConstants.Claims.Active] = false
                    }));
                }

                // Reject the introspection request if the caller doesn't
                // correspond to the client application the token was issued to.
                else if (ticket.IsRefreshToken() && ticket.HasPresenter() && !ticket.HasPresenter(request.ClientId))
                {
                    Options.Logger.LogWarning("The introspection request was rejected because the " +
                                              "refresh token was issued to a different client.");

                    return(await SendIntrospectionResponseAsync(request, new JObject {
                        [OpenIdConnectConstants.Claims.Active] = false
                    }));
                }
            }

            var notification = new HandleIntrospectionRequestContext(Context, Options, request, ticket);

            notification.Active = true;

            // Use the unique ticket identifier to populate the "jti" claim.
            notification.TokenId = ticket.GetTicketId();

            // Note: only set "token_type" when the received token is an access token.
            // See https://tools.ietf.org/html/rfc7662#section-2.2
            // and https://tools.ietf.org/html/rfc6749#section-5.1
            if (ticket.IsAccessToken())
            {
                notification.TokenType = OpenIdConnectConstants.TokenTypes.Bearer;
            }

            notification.Issuer  = Context.GetIssuer(Options);
            notification.Subject = ticket.Identity.GetClaim(ClaimTypes.NameIdentifier);

            notification.IssuedAt  = ticket.Properties.IssuedUtc;
            notification.ExpiresAt = ticket.Properties.ExpiresUtc;

            // Copy the audiences extracted from the "aud" claim.
            foreach (var audience in ticket.GetAudiences())
            {
                notification.Audiences.Add(audience);
            }

            // Note: non-metadata claims are only added if the caller is authenticated
            // AND is in the specified audiences, unless there's so explicit audience.
            if (!ticket.HasAudience() || (!string.IsNullOrEmpty(request.ClientId) && ticket.HasAudience(request.ClientId)))
            {
                notification.Username = ticket.Identity.Name;
                notification.Scope    = ticket.GetProperty(OpenIdConnectConstants.Properties.Scopes);

                // Potentially sensitive claims are only exposed to trusted callers
                // if the ticket corresponds to an access or identity token.
                if (ticket.IsAccessToken() || ticket.IsIdentityToken())
                {
                    foreach (var claim in ticket.Identity.Claims)
                    {
                        // Exclude standard claims, that are already handled via strongly-typed properties.
                        // Make sure to always update this list when adding new built-in claim properties.
                        if (string.Equals(claim.Type, ticket.Identity.NameClaimType, StringComparison.Ordinal) ||
                            string.Equals(claim.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal))
                        {
                            continue;
                        }

                        if (string.Equals(claim.Type, OpenIdConnectConstants.Claims.Audience, StringComparison.Ordinal) ||
                            string.Equals(claim.Type, OpenIdConnectConstants.Claims.ExpiresAt, StringComparison.Ordinal) ||
                            string.Equals(claim.Type, OpenIdConnectConstants.Claims.IssuedAt, StringComparison.Ordinal) ||
                            string.Equals(claim.Type, OpenIdConnectConstants.Claims.Issuer, StringComparison.Ordinal) ||
                            string.Equals(claim.Type, OpenIdConnectConstants.Claims.NotBefore, StringComparison.Ordinal) ||
                            string.Equals(claim.Type, OpenIdConnectConstants.Claims.Scope, StringComparison.Ordinal) ||
                            string.Equals(claim.Type, OpenIdConnectConstants.Claims.Subject, StringComparison.Ordinal) ||
                            string.Equals(claim.Type, OpenIdConnectConstants.Claims.TokenType, StringComparison.Ordinal))
                        {
                            continue;
                        }

                        string type;
                        // Try to resolve the short name associated with the claim type:
                        // if none can be found, the claim type is used as-is.
                        if (!JwtSecurityTokenHandler.OutboundClaimTypeMap.TryGetValue(claim.Type, out type))
                        {
                            type = claim.Type;
                        }

                        // Note: make sure to use the indexer
                        // syntax to avoid duplicate properties.
                        notification.Claims[type] = claim.Value;
                    }
                }
            }

            await Options.Provider.HandleIntrospectionRequest(notification);

            if (notification.HandledResponse)
            {
                return(true);
            }

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

            var response = new JObject();

            response.Add(OpenIdConnectConstants.Claims.Active, notification.Active);

            // Only add the other properties if
            // the token is considered as active.
            if (notification.Active)
            {
                if (!string.IsNullOrEmpty(notification.Issuer))
                {
                    response.Add(OpenIdConnectConstants.Claims.Issuer, notification.Issuer);
                }

                if (!string.IsNullOrEmpty(notification.Username))
                {
                    response.Add(OpenIdConnectConstants.Claims.Username, notification.Username);
                }

                if (!string.IsNullOrEmpty(notification.Subject))
                {
                    response.Add(OpenIdConnectConstants.Claims.Subject, notification.Subject);
                }

                if (!string.IsNullOrEmpty(notification.Scope))
                {
                    response.Add(OpenIdConnectConstants.Claims.Scope, notification.Scope);
                }

                if (notification.IssuedAt.HasValue)
                {
                    response.Add(OpenIdConnectConstants.Claims.IssuedAt,
                                 EpochTime.GetIntDate(notification.IssuedAt.Value.UtcDateTime));

                    response.Add(OpenIdConnectConstants.Claims.NotBefore,
                                 EpochTime.GetIntDate(notification.IssuedAt.Value.UtcDateTime));
                }

                if (notification.ExpiresAt.HasValue)
                {
                    response.Add(OpenIdConnectConstants.Claims.ExpiresAt,
                                 EpochTime.GetIntDate(notification.ExpiresAt.Value.UtcDateTime));
                }

                if (!string.IsNullOrEmpty(notification.TokenId))
                {
                    response.Add(OpenIdConnectConstants.Claims.JwtId, notification.TokenId);
                }

                if (!string.IsNullOrEmpty(notification.TokenType))
                {
                    response.Add(OpenIdConnectConstants.Claims.TokenType, notification.TokenType);
                }

                switch (notification.Audiences.Count)
                {
                case 0: break;

                case 1:
                    response.Add(OpenIdConnectConstants.Claims.Audience, notification.Audiences[0]);
                    break;

                default:
                    response.Add(OpenIdConnectConstants.Claims.Audience, JArray.FromObject(notification.Audiences));
                    break;
                }

                foreach (var claim in notification.Claims)
                {
                    // Ignore claims whose value is null.
                    if (claim.Value == null)
                    {
                        continue;
                    }

                    // Note: make sure to use the indexer
                    // syntax to avoid duplicate properties.
                    response[claim.Key] = claim.Value;
                }
            }

            return(await SendIntrospectionResponseAsync(request, response));
        }
        private async Task <string> SerializeAccessTokenAsync(
            ClaimsIdentity identity, AuthenticationProperties properties,
            OpenIdConnectRequest request, OpenIdConnectResponse response)
        {
            // Create a new identity 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 "access_token" are not included in the access token.
                return(claim.HasDestination(OpenIdConnectConstants.Destinations.AccessToken));
            });

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

            ticket.Properties.IssuedUtc  = Options.SystemClock.UtcNow;
            ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc +
                                           (ticket.GetAccessTokenLifetime() ?? Options.AccessTokenLifetime);

            ticket.SetUsage(OpenIdConnectConstants.Usages.AccessToken);
            ticket.SetAudiences(ticket.GetResources());

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

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

            var notification = new SerializeAccessTokenContext(Context, Options, request, response, ticket)
            {
                DataFormat           = Options.AccessTokenFormat,
                Issuer               = Context.GetIssuer(Options),
                SecurityTokenHandler = Options.AccessTokenHandler,
                SigningCredentials   = Options.SigningCredentials.FirstOrDefault()
            };

            await Options.Provider.SerializeAccessToken(notification);

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

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

            if (!notification.Audiences.Any())
            {
                Options.Logger.LogInformation("No explicit audience was associated with the access token.");
            }

            if (notification.SecurityTokenHandler == null)
            {
                return(notification.DataFormat?.Protect(ticket));
            }

            if (notification.SigningCredentials == null)
            {
                throw new InvalidOperationException("A signing key must be provided.");
            }

            // 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());

            // 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);
            }

            // Create a new claim per scope item, that will result
            // in a "scope" array being added in the access token.
            foreach (var scope in notification.Scopes)
            {
                ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Scope, scope);
            }

            // Note: when used as an access token, a JWT token doesn't have to expose a "sub" claim
            // but the name identifier claim is used as a substitute when it has been explicitly added.
            // See https://tools.ietf.org/html/rfc7519#section-4.1.2
            var subject = ticket.Identity.FindFirst(OpenIdConnectConstants.Claims.Subject);

            if (subject == null)
            {
                var identifier = ticket.Identity.FindFirst(ClaimTypes.NameIdentifier);
                if (identifier != null)
                {
                    ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Subject, identifier.Value);
                }
            }

            // 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 ticket.Identity.FindAll(ClaimTypes.NameIdentifier).ToArray())
            {
                ticket.Identity.RemoveClaim(claim);
            }

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

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

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

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

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

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

            if (ticket.Properties.IssuedUtc != null)
            {
                ticket.Identity.AddClaim(new Claim(
                                             OpenIdConnectConstants.Claims.IssuedAt,
                                             EpochTime.GetIntDate(ticket.Properties.IssuedUtc.Value.UtcDateTime).ToString(),
                                             ClaimValueTypes.Integer64));
            }

            var descriptor = new SecurityTokenDescriptor {
                Subject               = ticket.Identity,
                TokenIssuerName       = notification.Issuer,
                EncryptingCredentials = notification.EncryptingCredentials,
                SigningCredentials    = notification.SigningCredentials,
                Lifetime              = new Lifetime(
                    notification.Ticket.Properties.IssuedUtc?.UtcDateTime,
                    notification.Ticket.Properties.ExpiresUtc?.UtcDateTime)
            };

            var token = notification.SecurityTokenHandler.CreateToken(descriptor);

            return(notification.SecurityTokenHandler.WriteToken(token));
        }
        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 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.Properties.IssuedUtc  = Options.SystemClock.UtcNow;
            ticket.Properties.ExpiresUtc = ticket.Properties.IssuedUtc +
                                           (ticket.GetIdentityTokenLifetime() ?? Options.IdentityTokenLifetime);

            ticket.SetUsage(OpenIdConnectConstants.Usages.IdentityToken);

            // 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 (notification.HandledResponse || !string.IsNullOrEmpty(notification.IdentityToken))
            {
                return(notification.IdentityToken);
            }

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

            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.");
            }

            if (notification.SigningCredentials == null)
            {
                throw new InvalidOperationException("A signing key must be provided.");
            }

            // 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());

            // 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);
            }

            using (var algorithm = HashAlgorithm.Create(notification.SigningCredentials.DigestAlgorithm)) {
                // 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:
                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;
            }

            if (ticket.Properties.IssuedUtc != null)
            {
                ticket.Identity.AddClaim(new Claim(
                                             OpenIdConnectConstants.Claims.IssuedAt,
                                             EpochTime.GetIntDate(ticket.Properties.IssuedUtc.Value.UtcDateTime).ToString(),
                                             ClaimValueTypes.Integer64));
            }

            var descriptor = new SecurityTokenDescriptor {
                Subject            = ticket.Identity,
                TokenIssuerName    = notification.Issuer,
                SigningCredentials = notification.SigningCredentials,
                Lifetime           = new Lifetime(
                    notification.Ticket.Properties.IssuedUtc?.UtcDateTime,
                    notification.Ticket.Properties.ExpiresUtc?.UtcDateTime)
            };

            var token = notification.SecurityTokenHandler.CreateToken(descriptor);

            return(notification.SecurityTokenHandler.WriteToken(token));
        }
        private async Task <string> SerializeAccessTokenAsync(
            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.AccessTokenLifetime;
            }

            // Create a new identity 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 "access_token" are not included in the access token.
                return(claim.HasDestination(OpenIdConnectConstants.Destinations.AccessToken));
            });

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

            ticket.SetUsage(OpenIdConnectConstants.Usages.AccessToken);
            ticket.SetAudiences(ticket.GetResources());

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

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

            var notification = new SerializeAccessTokenContext(Context, Options, request, response, ticket)
            {
                DataFormat           = Options.AccessTokenFormat,
                Issuer               = Context.GetIssuer(Options),
                SecurityTokenHandler = Options.AccessTokenHandler,
                SigningCredentials   = Options.SigningCredentials.FirstOrDefault()
            };

            await Options.Provider.SerializeAccessToken(notification);

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

            if (notification.SecurityTokenHandler == null)
            {
                return(notification.DataFormat?.Protect(ticket));
            }

            // Store the "unique_id" property as a claim.
            ticket.Identity.AddClaim(notification.SecurityTokenHandler is JwtSecurityTokenHandler ?
                                     OpenIdConnectConstants.Claims.JwtId :
                                     OpenIdConnectConstants.Claims.TokenId, 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));
            }

            // Create a new claim per scope item, that will result
            // in a "scope" array being added in the access token.
            foreach (var scope in ticket.GetScopes())
            {
                ticket.Identity.AddClaim(OpenIdConnectConstants.Claims.Scope, scope);
            }

            var handler = notification.SecurityTokenHandler as JwtSecurityTokenHandler;

            if (handler != null)
            {
                // Note: when used as an access token, a JWT token doesn't have to expose a "sub" claim
                // but the name identifier claim is used as a substitute when it has been explicitly added.
                // See https://tools.ietf.org/html/rfc7519#section-4.1.2
                var subject = identity.FindFirst(OpenIdConnectConstants.Claims.Subject);
                if (subject == null)
                {
                    var identifier = identity.FindFirst(ClaimTypes.NameIdentifier);
                    if (identifier != null)
                    {
                        identity.AddClaim(OpenIdConnectConstants.Claims.Subject, identifier.Value);
                    }
                }

                // 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 ticket.Identity.FindAll(ClaimTypes.NameIdentifier).ToArray())
                {
                    ticket.Identity.RemoveClaim(claim);
                }

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

                // 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 access 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 = handler.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(handler.WriteToken(token));
            }

            else
            {
                var descriptor = new SecurityTokenDescriptor {
                    Subject               = ticket.Identity,
                    AppliesToAddress      = notification.Audiences.ElementAtOrDefault(0),
                    TokenIssuerName       = notification.Issuer,
                    EncryptingCredentials = notification.EncryptingCredentials,
                    SigningCredentials    = notification.SigningCredentials,
                    Lifetime              = new Lifetime(
                        notification.Ticket.Properties.IssuedUtc.Value.UtcDateTime,
                        notification.Ticket.Properties.ExpiresUtc.Value.UtcDateTime)
                };

                // When the encrypting credentials use an asymmetric key, replace them by a
                // EncryptedKeyEncryptingCredentials instance to generate a symmetric key.
                if (descriptor.EncryptingCredentials != null &&
                    descriptor.EncryptingCredentials.SecurityKey is AsymmetricSecurityKey)
                {
                    // Note: EncryptedKeyEncryptingCredentials automatically generates an in-memory key
                    // that will be encrypted using the original credentials and added to the resulting token
                    // if the security token handler fully supports token encryption (e.g SAML or SAML2).
                    descriptor.EncryptingCredentials = new EncryptedKeyEncryptingCredentials(
                        wrappingCredentials: notification.EncryptingCredentials, keySizeInBits: 256,
                        encryptionAlgorithm: SecurityAlgorithms.Aes256Encryption);
                }

                var token = notification.SecurityTokenHandler.CreateToken(descriptor);

                // Note: the security token is manually serialized to prevent
                // an exception from being thrown if the handler doesn't implement
                // the SecurityTokenHandler.WriteToken overload returning a string.
                var builder = new StringBuilder();
                using (var writer = XmlWriter.Create(builder, new XmlWriterSettings {
                    Encoding = new UTF8Encoding(false), OmitXmlDeclaration = true
                })) {
                    notification.SecurityTokenHandler.WriteToken(writer, token);
                }

                return(builder.ToString());
            }
        }
        private async Task <string> SerializeAccessTokenAsync(
            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.AccessTokenLifetime;
            }

            // Create a new principal 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 "access_token" are not included in the access token.
                return(claim.HasDestination(OpenIdConnectConstants.Destinations.AccessToken));
            });

            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.AccessToken);
            ticket.SetAudiences(ticket.GetResources());

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

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

            var notification = new SerializeAccessTokenContext(Context, Options, request, response, ticket)
            {
                DataFormat           = Options.AccessTokenFormat,
                Issuer               = Context.GetIssuer(Options),
                SecurityTokenHandler = Options.AccessTokenHandler,
                SigningCredentials   = Options.SigningCredentials.FirstOrDefault()
            };

            await Options.Provider.SerializeAccessToken(notification);

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

            if (notification.SecurityTokenHandler == null)
            {
                return(notification.DataFormat?.Protect(ticket));
            }

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

            // Store the "unique_id" property as a claim.
            identity.AddClaim(notification.SecurityTokenHandler is JwtSecurityTokenHandler ?
                              OpenIdConnectConstants.Claims.JwtId :
                              OpenIdConnectConstants.Claims.TokenId, ticket.GetTicketId());

            // 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));
            }

            // Create a new claim per scope item, that will result
            // in a "scope" array being added in the access token.
            foreach (var scope in ticket.GetScopes())
            {
                identity.AddClaim(OpenIdConnectConstants.Claims.Scope, scope);
            }

            var handler = notification.SecurityTokenHandler as JwtSecurityTokenHandler;

            if (handler != null)
            {
                // Note: when used as an access token, a JWT token doesn't have to expose a "sub" claim
                // but the name identifier claim is used as a substitute when it has been explicitly added.
                // See https://tools.ietf.org/html/rfc7519#section-4.1.2
                var subject = identity.FindFirst(OpenIdConnectConstants.Claims.Subject);
                if (subject == null)
                {
                    var identifier = identity.FindFirst(ClaimTypes.NameIdentifier);
                    if (identifier != null)
                    {
                        identity.AddClaim(OpenIdConnectConstants.Claims.Subject, identifier.Value);
                    }
                }

                // 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 audiences as claims.
                foreach (var audience in ticket.GetAudiences())
                {
                    identity.AddClaim(OpenIdConnectConstants.Claims.Audience, audience);
                }

                // 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:
                    Logger.LogWarning("Multiple presenters have been associated with the access 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 = handler.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(handler.WriteToken(token));
            }

            else
            {
                var token = notification.SecurityTokenHandler.CreateToken(new SecurityTokenDescriptor {
                    Subject            = identity,
                    Issuer             = notification.Issuer,
                    Audience           = notification.Audiences.ElementAtOrDefault(0),
                    SigningCredentials = notification.SigningCredentials,
                    IssuedAt           = notification.Ticket.Properties.IssuedUtc.Value.UtcDateTime,
                    NotBefore          = notification.Ticket.Properties.IssuedUtc.Value.UtcDateTime,
                    Expires            = notification.Ticket.Properties.ExpiresUtc.Value.UtcDateTime
                });

                // Note: the security token is manually serialized to prevent
                // an exception from being thrown if the handler doesn't implement
                // the SecurityTokenHandler.WriteToken overload returning a string.
                var builder = new StringBuilder();
                using (var writer = XmlWriter.Create(builder, new XmlWriterSettings {
                    Encoding = new UTF8Encoding(false), OmitXmlDeclaration = true
                })) {
                    notification.SecurityTokenHandler.WriteToken(writer, token);
                }

                return(builder.ToString());
            }
        }
        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);

            // 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))
            {
                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 == 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.
            identity.AddClaim(OpenIdConnectConstants.Claims.JwtId, ticket.GetTicketId());

            // 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(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))
            {
                identity.AddClaim(OpenIdConnectConstants.Claims.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(OpenIdConnectConstants.Claims.CodeHash,
                                      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(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:
                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.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 <bool> InvokeIntrospectionEndpointAsync()
        {
            OpenIdConnectRequest request;

            // See https://tools.ietf.org/html/rfc7662#section-2.1
            // and https://tools.ietf.org/html/rfc7662#section-4
            if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                request = new OpenIdConnectRequest(Request.Query);
            }

            else if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
            {
                // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization
                if (string.IsNullOrEmpty(Request.ContentType))
                {
                    Logger.LogError("The introspection request was rejected because " +
                                    "the mandatory 'Content-Type' header was missing.");

                    return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed introspection request has been received: " +
                                           "the mandatory 'Content-Type' header was missing from the POST request."
                    }));
                }

                // May have media/type; charset=utf-8, allow partial match.
                if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase))
                {
                    Logger.LogError("The introspection request was rejected because an invalid 'Content-Type' " +
                                    "header was received: {ContentType}.", Request.ContentType);

                    return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "A malformed introspection request has been received: " +
                                           "the 'Content-Type' header contained an unexcepted value. " +
                                           "Make sure to use 'application/x-www-form-urlencoded'."
                    }));
                }

                request = new OpenIdConnectRequest(await Request.ReadFormAsync());
            }

            else
            {
                Logger.LogError("The introspection request was rejected because an invalid " +
                                "HTTP method was received: {Method}.", Request.Method);

                return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "A malformed introspection request has been received: " +
                                       "make sure to use either GET or POST."
                }));
            }

            // Note: set the message type before invoking the ExtractIntrospectionRequest event.
            request.SetProperty(OpenIdConnectConstants.Properties.MessageType,
                                OpenIdConnectConstants.MessageTypes.IntrospectionRequest);

            // Insert the introspection request in the OWIN context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractIntrospectionRequestContext(Context, Options, request);
            await Options.Provider.ExtractIntrospectionRequest(@event);

            if (@event.HandledResponse)
            {
                Logger.LogDebug("The introspection request was handled in user code.");

                return(true);
            }

            else if (@event.Skipped)
            {
                Logger.LogDebug("The default introspection request handling was skipped from user code.");

                return(false);
            }

            else if (@event.IsRejected)
            {
                Logger.LogError("The introspection request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ @event.ErrorDescription);

                return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                {
                    Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = @event.ErrorDescription,
                    ErrorUri = @event.ErrorUri
                }));
            }

            Logger.LogInformation("The introspection request was successfully extracted " +
                                  "from the HTTP request: {Request}", request);

            if (string.IsNullOrWhiteSpace(request.Token))
            {
                return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "A malformed introspection request has been received: " +
                                       "a 'token' parameter with an access, refresh, or identity token is required."
                }));
            }

            // When client_id and client_secret are both null, try to extract them from the Authorization header.
            // See http://tools.ietf.org/html/rfc6749#section-2.3.1 and
            // http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
            if (string.IsNullOrEmpty(request.ClientId) && string.IsNullOrEmpty(request.ClientSecret))
            {
                var header = Request.Headers.Get("Authorization");
                if (!string.IsNullOrEmpty(header) && header.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
                {
                    try
                    {
                        var value = header.Substring("Basic ".Length).Trim();
                        var data  = Encoding.UTF8.GetString(Convert.FromBase64String(value));

                        var index = data.IndexOf(':');
                        if (index >= 0)
                        {
                            request.ClientId     = data.Substring(0, index);
                            request.ClientSecret = data.Substring(index + 1);
                        }
                    }

                    catch (FormatException) { }
                    catch (ArgumentException) { }
                }
            }

            var context = new ValidateIntrospectionRequestContext(Context, Options, request);
            await Options.Provider.ValidateIntrospectionRequest(context);

            // If the validation context was set as fully validated,
            // mark the OpenID Connect request as confidential.
            if (context.IsValidated)
            {
                request.SetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel,
                                    OpenIdConnectConstants.ConfidentialityLevels.Private);
            }

            if (context.HandledResponse)
            {
                Logger.LogDebug("The introspection request was handled in user code.");

                return(true);
            }

            else if (context.Skipped)
            {
                Logger.LogDebug("The default introspection request handling was skipped from user code.");

                return(false);
            }

            else if (context.IsRejected)
            {
                Logger.LogError("The introspection request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ context.ErrorDescription);

                return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                {
                    Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = context.ErrorDescription,
                    ErrorUri = context.ErrorUri
                }));
            }

            // Store the validated client_id as a request property.
            request.SetProperty(OpenIdConnectConstants.Properties.ClientId, context.ClientId);

            Logger.LogInformation("The introspection request was successfully validated.");

            AuthenticationTicket ticket = null;

            // Note: use the "token_type_hint" parameter to determine
            // the type of the token sent by the client application.
            // See https://tools.ietf.org/html/rfc7662#section-2.1
            switch (request.TokenTypeHint)
            {
            case OpenIdConnectConstants.TokenTypeHints.AccessToken:
                ticket = await DeserializeAccessTokenAsync(request.Token, request);

                break;

            case OpenIdConnectConstants.TokenTypeHints.AuthorizationCode:
                ticket = await DeserializeAuthorizationCodeAsync(request.Token, request);

                break;

            case OpenIdConnectConstants.TokenTypeHints.IdToken:
                ticket = await DeserializeIdentityTokenAsync(request.Token, request);

                break;

            case OpenIdConnectConstants.TokenTypeHints.RefreshToken:
                ticket = await DeserializeRefreshTokenAsync(request.Token, request);

                break;
            }

            // Note: if the token can't be found using "token_type_hint",
            // the search must be extended to all supported token types.
            // See https://tools.ietf.org/html/rfc7662#section-2.1
            if (ticket == null)
            {
                ticket = await DeserializeAccessTokenAsync(request.Token, request) ??
                         await DeserializeAuthorizationCodeAsync(request.Token, request) ??
                         await DeserializeIdentityTokenAsync(request.Token, request) ??
                         await DeserializeRefreshTokenAsync(request.Token, request);
            }

            if (ticket == null)
            {
                Logger.LogInformation("The introspection request was rejected because the token was invalid.");

                return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                {
                    [OpenIdConnectConstants.Parameters.Active] = false
                }));
            }

            // Note: unlike refresh or identity tokens that can only be validated by client applications,
            // access tokens can be validated by either resource servers or client applications:
            // in both cases, the caller must be authenticated if the ticket is marked as confidential.
            if (context.IsSkipped && ticket.IsConfidential())
            {
                Logger.LogError("The introspection request was rejected because the caller was not authenticated.");

                return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                {
                    [OpenIdConnectConstants.Parameters.Active] = false
                }));
            }

            // If the ticket is already expired, directly return active=false.
            if (ticket.Properties.ExpiresUtc.HasValue &&
                ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow)
            {
                Logger.LogInformation("The introspection request was rejected because the token was expired.");

                return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                {
                    [OpenIdConnectConstants.Parameters.Active] = false
                }));
            }

            // When a client_id can be inferred from the introspection request,
            // ensure that the client application is a valid audience/presenter.
            if (!string.IsNullOrEmpty(context.ClientId))
            {
                if (ticket.IsAuthorizationCode() && ticket.HasPresenter() && !ticket.HasPresenter(context.ClientId))
                {
                    Logger.LogError("The introspection request was rejected because the " +
                                    "authorization code was issued to a different client.");

                    return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                    {
                        [OpenIdConnectConstants.Parameters.Active] = false
                    }));
                }

                // Ensure the caller is listed as a valid audience or authorized presenter.
                else if (ticket.IsAccessToken() && ticket.HasAudience() && !ticket.HasAudience(context.ClientId) &&
                         ticket.HasPresenter() && !ticket.HasPresenter(context.ClientId))
                {
                    Logger.LogError("The introspection request was rejected because the access token " +
                                    "was issued to a different client or for another resource server.");

                    return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                    {
                        [OpenIdConnectConstants.Parameters.Active] = false
                    }));
                }

                // Reject the request if the caller is not listed as a valid audience.
                else if (ticket.IsIdentityToken() && ticket.HasAudience() && !ticket.HasAudience(context.ClientId))
                {
                    Logger.LogError("The introspection request was rejected because the " +
                                    "identity token was issued to a different client.");

                    return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                    {
                        [OpenIdConnectConstants.Parameters.Active] = false
                    }));
                }

                // Reject the introspection request if the caller doesn't
                // correspond to the client application the token was issued to.
                else if (ticket.IsRefreshToken() && ticket.HasPresenter() && !ticket.HasPresenter(context.ClientId))
                {
                    Logger.LogError("The introspection request was rejected because the " +
                                    "refresh token was issued to a different client.");

                    return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                    {
                        [OpenIdConnectConstants.Parameters.Active] = false
                    }));
                }
            }

            var notification = new HandleIntrospectionRequestContext(Context, Options, request, ticket);

            notification.Active  = true;
            notification.Issuer  = Context.GetIssuer(Options);
            notification.Subject = ticket.Identity.GetClaim(OpenIdConnectConstants.Claims.Subject);

            // Use the unique ticket identifier to populate the "jti" claim.
            notification.TokenId = ticket.GetTicketId();

            // Note: only set "token_type" when the received token is an access token.
            // See https://tools.ietf.org/html/rfc7662#section-2.2
            // and https://tools.ietf.org/html/rfc6749#section-5.1
            if (ticket.IsAccessToken())
            {
                notification.TokenType = OpenIdConnectConstants.TokenTypes.Bearer;
            }

            notification.IssuedAt  = ticket.Properties.IssuedUtc;
            notification.NotBefore = ticket.Properties.IssuedUtc;
            notification.ExpiresAt = ticket.Properties.ExpiresUtc;

            // Copy the audiences extracted from the "aud" claim.
            notification.Audiences.UnionWith(ticket.GetAudiences());

            // Note: non-metadata claims are only added if the caller is authenticated
            // AND is in the specified audiences, unless there's so explicit audience.
            if (!ticket.HasAudience() || (!string.IsNullOrEmpty(context.ClientId) && ticket.HasAudience(context.ClientId)))
            {
                notification.Username = ticket.Identity.Name;
                notification.Scopes.UnionWith(ticket.GetScopes());

                // Potentially sensitive claims are only exposed to trusted callers
                // if the ticket corresponds to an access or identity token.
                if (ticket.IsAccessToken() || ticket.IsIdentityToken())
                {
                    foreach (var grouping in ticket.Identity.Claims.GroupBy(claim => claim.Type))
                    {
                        // Exclude standard claims, that are already handled via strongly-typed properties.
                        // Make sure to always update this list when adding new built-in claim properties.
                        var type = grouping.Key;
                        switch (type)
                        {
                        case OpenIdConnectConstants.Claims.Audience:
                        case OpenIdConnectConstants.Claims.ExpiresAt:
                        case OpenIdConnectConstants.Claims.IssuedAt:
                        case OpenIdConnectConstants.Claims.Issuer:
                        case OpenIdConnectConstants.Claims.NotBefore:
                        case OpenIdConnectConstants.Claims.Scope:
                        case OpenIdConnectConstants.Claims.Subject:
                        case OpenIdConnectConstants.Claims.TokenType:
                            continue;
                        }

                        var claims = grouping.ToArray();

                        switch (claims.Length)
                        {
                        case 0: continue;

                        // When there's only one claim with the same type, directly
                        // convert the claim as an OpenIdConnectParameter instance,
                        // whose token type is determined from the claim value type.
                        case 1:
                        {
                            notification.Claims[type] = claims[0].AsParameter();

                            continue;
                        }

                        // When multiple claims share the same type, convert all the claims
                        // to OpenIdConnectParameter instances, retrieve the underlying
                        // JSON values and add everything to a new JSON array.
                        default:
                        {
                            notification.Claims[type] = new JArray(claims.Select(claim => claim.AsParameter().Value));

                            continue;
                        }
                        }
                    }
                }
            }

            await Options.Provider.HandleIntrospectionRequest(notification);

            if (notification.HandledResponse)
            {
                Logger.LogDebug("The introspection request was handled in user code.");

                return(true);
            }

            else if (notification.Skipped)
            {
                Logger.LogDebug("The default introspection request handling was skipped from user code.");

                return(false);
            }

            else if (notification.IsRejected)
            {
                Logger.LogError("The introspection request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ notification.ErrorDescription);

                return(await SendIntrospectionResponseAsync(new OpenIdConnectResponse
                {
                    Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = notification.ErrorDescription,
                    ErrorUri = notification.ErrorUri
                }));
            }

            var response = new OpenIdConnectResponse
            {
                [OpenIdConnectConstants.Claims.Active] = notification.Active
            };

            // Only add the other properties if
            // the token is considered as active.
            if (notification.Active)
            {
                response[OpenIdConnectConstants.Claims.Issuer]    = notification.Issuer;
                response[OpenIdConnectConstants.Claims.Username]  = notification.Username;
                response[OpenIdConnectConstants.Claims.Subject]   = notification.Subject;
                response[OpenIdConnectConstants.Claims.Scope]     = string.Join(" ", notification.Scopes);
                response[OpenIdConnectConstants.Claims.JwtId]     = notification.TokenId;
                response[OpenIdConnectConstants.Claims.TokenType] = notification.TokenType;

                if (notification.IssuedAt != null)
                {
                    response[OpenIdConnectConstants.Claims.IssuedAt] =
                        EpochTime.GetIntDate(notification.IssuedAt.Value.UtcDateTime);
                }

                if (notification.NotBefore != null)
                {
                    response[OpenIdConnectConstants.Claims.NotBefore] =
                        EpochTime.GetIntDate(notification.NotBefore.Value.UtcDateTime);
                }

                if (notification.ExpiresAt != null)
                {
                    response[OpenIdConnectConstants.Claims.ExpiresAt] =
                        EpochTime.GetIntDate(notification.ExpiresAt.Value.UtcDateTime);
                }

                switch (notification.Audiences.Count)
                {
                case 0: break;

                case 1:
                    response[OpenIdConnectConstants.Claims.Audience] = notification.Audiences.ElementAt(0);
                    break;

                default:
                    response[OpenIdConnectConstants.Claims.Audience] = new JArray(notification.Audiences);
                    break;
                }

                foreach (var claim in notification.Claims)
                {
                    response.SetParameter(claim.Key, claim.Value);
                }
            }

            return(await SendIntrospectionResponseAsync(response));
        }