private async Task <bool> InvokeCryptographyEndpointAsync()
        {
            // Metadata requests must be made via GET.
            // See http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
            if (!string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                Logger.LogError("The cryptography request was rejected because an invalid " +
                                "HTTP method was specified: {Method}.", Request.Method);

                return(await SendCryptographyResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The specified HTTP method is not valid."
                }));
            }

            var request = new OpenIdConnectRequest(Request.Query);

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

            // Store the cryptography request in the OWIN context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractCryptographyRequestContext(Context, Options, request);
            await Options.Provider.ExtractCryptographyRequest(@event);

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

                return(true);
            }

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

                return(false);
            }

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

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

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

            var context = new ValidateCryptographyRequestContext(Context, Options, request);
            await Options.Provider.ValidateCryptographyRequest(context);

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

                return(true);
            }

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

                return(false);
            }

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

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

            var notification = new HandleCryptographyRequestContext(Context, Options, request);

            foreach (var credentials in Options.SigningCredentials)
            {
                // If the signing key is not an asymmetric key, ignore it.
                if (!(credentials.Key is AsymmetricSecurityKey))
                {
                    Logger.LogDebug("A non-asymmetric signing key of type '{Type}' was excluded " +
                                    "from the key set.", credentials.Key.GetType().FullName);

                    continue;
                }

#if SUPPORTS_ECDSA
                if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) &&
                    !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) &&
                    !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) &&
                    !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
                {
                    Logger.LogInformation("An unsupported signing key of type '{Type}' was ignored and excluded " +
                                          "from the key set. Only RSA and ECDSA asymmetric security keys can be " +
                                          "exposed via the JWKS endpoint.", credentials.Key.GetType().Name);

                    continue;
                }
#else
                if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256))
                {
                    Logger.LogInformation("An unsupported signing key of type '{Type}' was ignored and excluded " +
                                          "from the key set. Only RSA asymmetric security keys can be exposed " +
                                          "via the JWKS endpoint.", credentials.Key.GetType().Name);

                    continue;
                }
#endif

                var key = new JsonWebKey
                {
                    Use = JsonWebKeyUseNames.Sig,

                    // Resolve the JWA identifier from the algorithm specified in the credentials.
                    Alg = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.Algorithm),

                    // Use the key identifier specified in the signing credentials.
                    Kid = credentials.Kid,
                };

                if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256))
                {
                    RSA algorithm = null;

                    // Note: IdentityModel 5 doesn't expose a method allowing to retrieve the underlying algorithm
                    // from a generic asymmetric security key. To work around this limitation, try to cast
                    // the security key to the built-in IdentityModel types to extract the required RSA instance.
                    // See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/395
                    if (credentials.Key is X509SecurityKey x509SecurityKey)
                    {
                        algorithm = x509SecurityKey.PublicKey as RSA;
                    }

                    else if (credentials.Key is RsaSecurityKey rsaSecurityKey)
                    {
                        algorithm = rsaSecurityKey.Rsa;

                        // If no RSA instance can be found, create one using
                        // the RSA parameters attached to the security key.
                        if (algorithm == null)
                        {
                            var rsa = RSA.Create();
                            rsa.ImportParameters(rsaSecurityKey.Parameters);
                            algorithm = rsa;
                        }
                    }

                    // Skip the key if an algorithm instance cannot be extracted.
                    if (algorithm == null)
                    {
                        Logger.LogWarning("A signing key was ignored because it was unable " +
                                          "to provide the requested algorithm instance.");

                        continue;
                    }

                    // Export the RSA public key to create a new JSON Web Key
                    // exposing the exponent and the modulus parameters.
                    var parameters = algorithm.ExportParameters(includePrivateParameters: false);

                    Debug.Assert(parameters.Exponent != null &&
                                 parameters.Modulus != null,
                                 "RSA.ExportParameters() shouldn't return null parameters.");

                    key.Kty = JsonWebAlgorithmsKeyTypes.RSA;

                    // Note: both E and N must be base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7518#section-6.3.1.1
                    key.E = Base64UrlEncoder.Encode(parameters.Exponent);
                    key.N = Base64UrlEncoder.Encode(parameters.Modulus);
                }

#if SUPPORTS_ECDSA
                else if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) ||
                         credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) ||
                         credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512))
                {
                    ECDsa algorithm = null;

                    if (credentials.Key is X509SecurityKey x509SecurityKey)
                    {
                        algorithm = x509SecurityKey.PublicKey as ECDsa;
                    }

                    else if (credentials.Key is ECDsaSecurityKey ecdsaSecurityKey)
                    {
                        algorithm = ecdsaSecurityKey.ECDsa;
                    }

                    // Skip the key if an algorithm instance cannot be extracted.
                    if (algorithm == null)
                    {
                        Logger.LogWarning("A signing key was ignored because it was unable " +
                                          "to provide the requested algorithm instance.");

                        continue;
                    }

                    // Export the ECDsa public key to create a new JSON Web Key
                    // exposing the coordinates of the point on the curve.
                    var parameters = algorithm.ExportParameters(includePrivateParameters: false);

                    Debug.Assert(parameters.Q.X != null &&
                                 parameters.Q.Y != null,
                                 "ECDsa.ExportParameters() shouldn't return null coordinates.");

                    key.Kty = JsonWebAlgorithmsKeyTypes.EllipticCurve;
                    key.Crv = OpenIdConnectServerHelpers.GetJwtAlgorithmCurve(parameters.Curve);

                    // Note: both X and Y must be base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7518#section-6.2.1.2
                    key.X = Base64UrlEncoder.Encode(parameters.Q.X);
                    key.Y = Base64UrlEncoder.Encode(parameters.Q.Y);
                }
#endif

                // If the signing key is embedded in a X.509 certificate, set
                // the x5t and x5c parameters using the certificate details.
                var certificate = (credentials.Key as X509SecurityKey)?.Certificate;
                if (certificate != null)
                {
                    // x5t must be base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7517#section-4.8
                    key.X5t = Base64UrlEncoder.Encode(certificate.GetCertHash());

                    // Unlike E or N, the certificates contained in x5c
                    // must be base64-encoded and not base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7517#section-4.7
                    key.X5c.Add(Convert.ToBase64String(certificate.RawData));
                }

                notification.Keys.Add(key);
            }

            await Options.Provider.HandleCryptographyRequest(notification);

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

                return(true);
            }

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

                return(false);
            }

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

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

            var keys = new JArray();

            foreach (var key in notification.Keys)
            {
                var item = new JObject();

                // Ensure a key type has been provided.
                // See https://tools.ietf.org/html/rfc7517#section-4.1
                if (string.IsNullOrEmpty(key.Kty))
                {
                    Logger.LogError("A JSON Web Key was excluded from the key set because " +
                                    "it didn't contain the mandatory 'kid' parameter.");

                    continue;
                }

                // Create a dictionary associating the
                // JsonWebKey components with their values.
                var parameters = new Dictionary <string, string>
                {
                    [JsonWebKeyParameterNames.Kid] = key.Kid,
                    [JsonWebKeyParameterNames.Use] = key.Use,
                    [JsonWebKeyParameterNames.Kty] = key.Kty,
                    [JsonWebKeyParameterNames.Alg] = key.Alg,
                    [JsonWebKeyParameterNames.Crv] = key.Crv,
                    [JsonWebKeyParameterNames.E]   = key.E,
                    [JsonWebKeyParameterNames.N]   = key.N,
                    [JsonWebKeyParameterNames.X]   = key.X,
                    [JsonWebKeyParameterNames.Y]   = key.Y,
                    [JsonWebKeyParameterNames.X5t] = key.X5t,
                    [JsonWebKeyParameterNames.X5u] = key.X5u
                };

                foreach (var parameter in parameters)
                {
                    if (!string.IsNullOrEmpty(parameter.Value))
                    {
                        item.Add(parameter.Key, parameter.Value);
                    }
                }

                if (key.KeyOps.Count != 0)
                {
                    item.Add(JsonWebKeyParameterNames.KeyOps, new JArray(key.KeyOps));
                }

                if (key.X5c.Count != 0)
                {
                    item.Add(JsonWebKeyParameterNames.X5c, new JArray(key.X5c));
                }

                keys.Add(item);
            }

            // Note: AddParameter() is used here to ensure the mandatory "keys" node
            // is returned to the caller, even if the key set doesn't expose any key.
            // See https://tools.ietf.org/html/rfc7517#section-5 for more information.
            var response = new OpenIdConnectResponse();
            response.AddParameter(OpenIdConnectConstants.Parameters.Keys, keys);

            return(await SendCryptographyResponseAsync(response));
        }
Example #2
0
        private async Task <bool> InvokeTokenEndpointAsync()
        {
            if (!string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
            {
                Options.Logger.LogError("The token request was rejected because an invalid " +
                                        "HTTP method was received: {Method}.", Request.Method);

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "A malformed token request has been received: make sure to use POST."
                }));
            }

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

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "A malformed token 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 token request was rejected because an invalid 'Content-Type' " +
                                        "header was received: {ContentType}.", Request.ContentType);

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

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

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

            // Store the token request in the OWIN context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractTokenRequestContext(Context, Options, request);
            await Options.Provider.ExtractTokenRequest(@event);

            if (@event.HandledResponse)
            {
                return(true);
            }

            else if (@event.Skipped)
            {
                return(false);
            }

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

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

            // Reject token requests missing the mandatory grant_type parameter.
            if (string.IsNullOrEmpty(request.GrantType))
            {
                Options.Logger.LogError("The token request was rejected because the grant type was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'grant_type' parameter was missing.",
                }));
            }

            // Reject grant_type=authorization_code requests if the authorization endpoint is disabled.
            else if (request.IsAuthorizationCodeGrantType() && !Options.AuthorizationEndpointPath.HasValue)
            {
                Options.Logger.LogError("The token request was rejected because the authorization code grant was disabled.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                    ErrorDescription = "The authorization code grant is not allowed by this authorization server."
                }));
            }

            // Reject grant_type=authorization_code requests missing the authorization code.
            // See https://tools.ietf.org/html/rfc6749#section-4.1.3
            else if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(request.Code))
            {
                Options.Logger.LogError("The token request was rejected because the authorization code was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'code' parameter was missing."
                }));
            }

            // Reject grant_type=refresh_token requests missing the refresh token.
            // See https://tools.ietf.org/html/rfc6749#section-6
            else if (request.IsRefreshTokenGrantType() && string.IsNullOrEmpty(request.RefreshToken))
            {
                Options.Logger.LogError("The token request was rejected because the refresh token was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'refresh_token' parameter was missing."
                }));
            }

            // Reject grant_type=password requests missing username or password.
            // See https://tools.ietf.org/html/rfc6749#section-4.3.2
            else if (request.IsPasswordGrantType() && (string.IsNullOrEmpty(request.Username) ||
                                                       string.IsNullOrEmpty(request.Password)))
            {
                Options.Logger.LogError("The token request was rejected because the resource owner credentials were missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'username' and/or 'password' parameters " +
                                       "was/were missing from the request message."
                }));
            }

            // 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 ValidateTokenRequestContext(Context, Options, request);
            await Options.Provider.ValidateTokenRequest(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)
            {
                return(true);
            }

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

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

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

            // Reject grant_type=client_credentials requests if validation was skipped.
            else if (context.IsSkipped && request.IsClientCredentialsGrantType())
            {
                Options.Logger.LogError("The token request must be fully validated to use the client_credentials grant type.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = "Client authentication is required when using client_credentials."
                }));
            }

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

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

            // At this stage, client_id cannot be null for grant_type=authorization_code requests,
            // as it must either be set in the ValidateTokenRequest notification
            // by the developer or manually flowed by non-confidential client applications.
            // See https://tools.ietf.org/html/rfc6749#section-4.1.3
            if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(request.ClientId))
            {
                Options.Logger.LogError("The token request was rejected because the mandatory 'client_id' was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "client_id was missing from the token request"
                }));
            }

            AuthenticationTicket ticket = null;

            // See http://tools.ietf.org/html/rfc6749#section-4.1
            // and http://tools.ietf.org/html/rfc6749#section-4.1.3 (authorization code grant).
            // See http://tools.ietf.org/html/rfc6749#section-6 (refresh token grant).
            if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
            {
                ticket = request.IsAuthorizationCodeGrantType() ?
                         await DeserializeAuthorizationCodeAsync(request.Code, request) :
                         await DeserializeRefreshTokenAsync(request.RefreshToken, request);

                if (ticket == null)
                {
                    Options.Logger.LogError("The token request was rejected because the " +
                                            "authorization code or the refresh token was invalid.");

                    return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "Invalid ticket"
                    }));
                }

                // If the client was fully authenticated when retrieving its refresh token,
                // the current request must be rejected if client authentication was not enforced.
                if (request.IsRefreshTokenGrantType() && !context.IsValidated && ticket.IsConfidential())
                {
                    Options.Logger.LogError("The token request was rejected because client authentication " +
                                            "was required to use the confidential refresh token.");

                    return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "Client authentication is required to use this ticket"
                    }));
                }

                if (ticket.Properties.ExpiresUtc.HasValue &&
                    ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow)
                {
                    Options.Logger.LogError("The token request was rejected because the " +
                                            "authorization code or the refresh token was expired.");

                    return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "Expired ticket"
                    }));
                }

                // Note: presenters may be empty during a grant_type=refresh_token request if the refresh token
                // was issued to a public client but cannot be null for an authorization code grant request.
                var presenters = ticket.GetPresenters();
                if (request.IsAuthorizationCodeGrantType() && !presenters.Any())
                {
                    Options.Logger.LogError("The token request was rejected because the authorization " +
                                            "code didn't contain any valid presenter.");

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

                // Ensure the authorization code/refresh token was issued to the client application making the token request.
                // Note: when using the refresh token grant, client_id is optional but must validated if present.
                // As a consequence, this check doesn't depend on the actual status of client authentication.
                // See https://tools.ietf.org/html/rfc6749#section-6
                // and http://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken
                if (!string.IsNullOrEmpty(request.ClientId) && presenters.Any() &&
                    !presenters.Contains(request.ClientId, StringComparer.Ordinal))
                {
                    Options.Logger.LogError("The token request was rejected because the authorization " +
                                            "code was issued to a different client application.");

                    return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "Ticket does not contain matching client_id"
                    }));
                }

                // Validate the redirect_uri flowed by the client application during this token request.
                // Note: for pure OAuth2 requests, redirect_uri is only mandatory if the authorization request
                // contained an explicit redirect_uri. OpenID Connect requests MUST include a redirect_uri
                // but the specifications allow proceeding the token request without returning an error
                // if the authorization request didn't contain an explicit redirect_uri.
                // See https://tools.ietf.org/html/rfc6749#section-4.1.3
                // and http://openid.net/specs/openid-connect-core-1_0.html#TokenRequestValidation
                var address = ticket.GetProperty(OpenIdConnectConstants.Properties.RedirectUri);
                if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(address))
                {
                    ticket.SetProperty(OpenIdConnectConstants.Properties.RedirectUri, null);

                    if (string.IsNullOrEmpty(request.RedirectUri))
                    {
                        Options.Logger.LogError("The token request was rejected because the mandatory 'redirect_uri' " +
                                                "parameter was missing from the grant_type=authorization_code request.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidRequest,
                            ErrorDescription = "redirect_uri was missing from the token request"
                        }));
                    }

                    else if (!string.Equals(address, request.RedirectUri, StringComparison.Ordinal))
                    {
                        Options.Logger.LogError("The token request was rejected because the 'redirect_uri' " +
                                                "parameter didn't correspond to the expected value.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "Authorization code does not contain matching redirect_uri"
                        }));
                    }
                }

                // If a code challenge was initially sent in the authorization request and associated with the
                // code, validate the code verifier to ensure the token request is sent by a legit caller.
                var challenge = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallenge);
                if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(challenge))
                {
                    ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallenge, null);

                    // Get the code verifier from the token request.
                    // If it cannot be found, return an invalid_grant error.
                    var verifier = request.CodeVerifier;
                    if (string.IsNullOrEmpty(verifier))
                    {
                        Options.Logger.LogError("The token request was rejected because the required 'code_verifier' " +
                                                "parameter was missing from the grant_type=authorization_code request.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidRequest,
                            ErrorDescription = "The required 'code_verifier' was missing from the token request."
                        }));
                    }

                    // Note: the code challenge method is always validated when receiving the authorization request.
                    var method = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod);
                    ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod, null);

                    Debug.Assert(string.IsNullOrEmpty(method) ||
                                 string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Plain, StringComparison.Ordinal) ||
                                 string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal),
                                 "The specified code challenge method should be supported.");

                    // If the S256 challenge method was used, compute the hash corresponding to the code verifier.
                    if (string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal))
                    {
                        using (var algorithm = SHA256.Create()) {
                            // Compute the SHA-256 hash of the code verifier and encode it using base64-url.
                            // See https://tools.ietf.org/html/rfc7636#section-4.6 for more information.
                            var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(request.CodeVerifier));

                            verifier = Base64UrlEncoder.Encode(hash);
                        }
                    }

                    // Compare the verifier and the code challenge: if the two don't match, return an error.
                    // Note: to prevent timing attacks, a time-constant comparer is always used.
                    if (!OpenIdConnectServerHelpers.AreEqual(verifier, challenge))
                    {
                        Options.Logger.LogError("The token request was rejected because the 'code_verifier' was invalid.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "The specified 'code_verifier' was invalid."
                        }));
                    }
                }

                if (request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(request.Resource))
                {
                    // When an explicit resource parameter has been included in the token request
                    // but was missing from the initial request, the request MUST be rejected.
                    var resources = ticket.GetResources();
                    if (!resources.Any())
                    {
                        Options.Logger.LogError("The token request was rejected because the 'resource' parameter was not allowed.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "Token request cannot contain a resource parameter " +
                                               "if the authorization request didn't contain one"
                        }));
                    }

                    // When an explicit resource parameter has been included in the token request,
                    // the authorization server MUST ensure that it doesn't contain resources
                    // that were not allowed during the initial authorization/token request.
                    else if (!new HashSet <string>(resources).IsSupersetOf(request.GetResources()))
                    {
                        Options.Logger.LogError("The token request was rejected because the 'resource' parameter was not valid.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "Token request doesn't contain a valid resource parameter"
                        }));
                    }
                }

                if (request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(request.Scope))
                {
                    // When an explicit scope parameter has been included in the token request
                    // but was missing from the initial request, the request MUST be rejected.
                    // See http://tools.ietf.org/html/rfc6749#section-6
                    var scopes = ticket.GetScopes();
                    if (!scopes.Any())
                    {
                        Options.Logger.LogError("The token request was rejected because the 'scope' parameter was not allowed.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "Token request cannot contain a scope parameter " +
                                               "if the authorization request didn't contain one"
                        }));
                    }

                    // When an explicit scope parameter has been included in the token request,
                    // the authorization server MUST ensure that it doesn't contain scopes
                    // that were not allowed during the initial authorization/token request.
                    // See https://tools.ietf.org/html/rfc6749#section-6
                    else if (!new HashSet <string>(scopes).IsSupersetOf(request.GetScopes()))
                    {
                        Options.Logger.LogError("The token request was rejected because the 'scope' parameter was not valid.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "Token request doesn't contain a valid scope parameter"
                        }));
                    }
                }
            }

            var notification = new HandleTokenRequestContext(Context, Options, request, ticket);
            await Options.Provider.HandleTokenRequest(notification);

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

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

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

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

            // Flow the changes made to the ticket.
            ticket = notification.Ticket;

            // Ensure an authentication ticket has been provided or return
            // an error code indicating that the grant type is not supported.
            if (ticket == null)
            {
                Options.Logger.LogError("The token request was rejected because no authentication " +
                                        "ticket was returned by application code.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                    ErrorDescription = "The specified grant_type parameter is not supported."
                }));
            }

            return(await HandleSignInAsync(ticket));
        }
        private async Task <bool> InvokeCryptographyEndpointAsync()
        {
            // Metadata requests must be made via GET.
            // See http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
            if (!string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                Options.Logger.LogError("The discovery request was rejected because an invalid " +
                                        "HTTP method was used: {Method}.", Request.Method);

                return(await SendCryptographyResponseAsync(null, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "Invalid HTTP method: make sure to use GET."
                }));
            }

            var request = new OpenIdConnectMessage(Request.Query);

            var context = new ValidateCryptographyRequestContext(Context, Options);
            await Options.Provider.ValidateCryptographyRequest(context);

            // Stop processing the request if Validated was not called.
            if (!context.IsValidated)
            {
                Options.Logger.LogError("The discovery request was rejected with the following error: {Error} ; {Description}",
                                        /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                        /* Description: */ context.ErrorDescription);

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

            var notification = new HandleCryptographyRequestContext(Context, Options, request);

            foreach (var credentials in Options.EncryptingCredentials)
            {
                // Ignore the key if it's not supported.
                if (!(credentials.SecurityKey is AsymmetricSecurityKey) ||
                    (!credentials.SecurityKey.IsSupportedAlgorithm(SecurityAlgorithms.RsaOaepKeyWrap) &&
                     !credentials.SecurityKey.IsSupportedAlgorithm(SecurityAlgorithms.RsaV15KeyWrap)))
                {
                    Options.Logger.LogInformation("An unsupported encryption key was ignored and excluded " +
                                                  "from the key set: {Type}. Only asymmetric security keys " +
                                                  "supporting RSA1_5 or RSA-OAEP can be exposed via the JWKS " +
                                                  "endpoint.", credentials.SecurityKey.GetType().Name);

                    continue;
                }

                // Try to extract a key identifier from the credentials.
                LocalIdKeyIdentifierClause identifier = null;
                credentials.SecurityKeyIdentifier?.TryFind(out identifier);

                // Resolve the underlying algorithm from the security key.
                var algorithm = (RSA)((AsymmetricSecurityKey)credentials.SecurityKey)
                                .GetAsymmetricAlgorithm(
                    algorithm: SecurityAlgorithms.RsaOaepKeyWrap,
                    privateKey: false);

                // Skip the key if a RSA instance cannot be retrieved.
                if (algorithm == null)
                {
                    Options.Logger.LogError("An encryption key was ignored because it was unable " +
                                            "to provide the requested RSA instance.");

                    continue;
                }

                // Export the RSA public key to create a new JSON Web Key
                // exposing the exponent and the modulus parameters.
                var parameters = algorithm.ExportParameters(includePrivateParameters: false);
                Debug.Assert(parameters.Exponent != null, "A null exponent was returned by RSA.ExportParameters()");
                Debug.Assert(parameters.Modulus != null, "A null modulus was returned by RSA.ExportParameters()");

                var key = new JsonWebKey {
                    Use = JsonWebKeyUseNames.Enc,
                    Kty = JsonWebAlgorithmsKeyTypes.RSA,

                    // Resolve the JWA identifier from the algorithm specified in the credentials.
                    Alg = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.Algorithm),

                    // Use the key identifier specified
                    // in the signing credentials.
                    Kid = identifier.LocalId,

                    // Both E and N must be base64url-encoded.
                    // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#appendix-A.1
                    E = Base64UrlEncoder.Encode(parameters.Exponent),
                    N = Base64UrlEncoder.Encode(parameters.Modulus)
                };

                X509Certificate2 x509Certificate = null;

                // Determine whether the encrypting credentials are directly based on a X.509 certificate.
                var x509EncryptingCredentials = credentials as X509EncryptingCredentials;
                if (x509EncryptingCredentials != null)
                {
                    x509Certificate = x509EncryptingCredentials.Certificate;
                }

                // Skip looking for a X509SecurityKey in EncryptingCredentials.SecurityKey
                // if a certificate has been found in the EncryptingCredentials instance.
                if (x509Certificate == null)
                {
                    // Determine whether the security key is an asymmetric key embedded in a X.509 certificate.
                    var x509SecurityKey = credentials.SecurityKey as X509SecurityKey;
                    if (x509SecurityKey != null)
                    {
                        x509Certificate = x509SecurityKey.Certificate;
                    }
                }

                // Skip looking for a X509AsymmetricSecurityKey in EncryptingCredentials.SecurityKey
                // if a certificate has been found in EncryptingCredentials or EncryptingCredentials.SecurityKey.
                if (x509Certificate == null)
                {
                    // Determine whether the security key is an asymmetric key embedded in a X.509 certificate.
                    var x509AsymmetricSecurityKey = credentials.SecurityKey as X509AsymmetricSecurityKey;
                    if (x509AsymmetricSecurityKey != null)
                    {
                        // The X.509 certificate is not directly accessible when using X509AsymmetricSecurityKey.
                        // Reflection is the only way to get the certificate used to create the security key.
                        var field = typeof(X509AsymmetricSecurityKey).GetField(
                            name: "certificate",
                            bindingAttr: BindingFlags.Instance | BindingFlags.NonPublic);
                        Debug.Assert(field != null);

                        x509Certificate = (X509Certificate2)field.GetValue(x509AsymmetricSecurityKey);
                    }
                }

                // If the encryption key is embedded in a X.509 certificate, set
                // the x5t and x5c parameters using the certificate details.
                if (x509Certificate != null)
                {
                    // x5t must be base64url-encoded.
                    // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#section-4.8
                    key.X5t = Base64UrlEncoder.Encode(x509Certificate.GetCertHash());

                    // Unlike E or N, the certificates contained in x5c
                    // must be base64-encoded and not base64url-encoded.
                    // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#section-4.7
                    key.X5c.Add(Convert.ToBase64String(x509Certificate.RawData));
                }

                notification.Keys.Add(key);
            }

            foreach (var credentials in Options.SigningCredentials)
            {
                // Ignore the key if it's not supported.
                if (!(credentials.SigningKey is AsymmetricSecurityKey) ||
                    !credentials.SigningKey.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256Signature))
                {
                    Options.Logger.LogInformation("An unsupported signing key was ignored and excluded " +
                                                  "from the key set: {Type}. Only asymmetric security keys " +
                                                  "supporting RS256, RS384 or RS512 can be exposed " +
                                                  "via the JWKS endpoint.", credentials.SigningKey.GetType().Name);

                    continue;
                }

                // Try to extract a key identifier from the credentials.
                LocalIdKeyIdentifierClause identifier = null;
                credentials.SigningKeyIdentifier?.TryFind(out identifier);

                // Resolve the underlying algorithm from the security key.
                var algorithm = (RSA)((AsymmetricSecurityKey)credentials.SigningKey)
                                .GetAsymmetricAlgorithm(
                    algorithm: SecurityAlgorithms.RsaOaepKeyWrap,
                    privateKey: false);

                // Skip the key if a RSA instance cannot be retrieved.
                if (algorithm == null)
                {
                    Options.Logger.LogError("A signing key was ignored because it was unable " +
                                            "to provide the requested RSA instance.");

                    continue;
                }

                // Export the RSA public key to create a new JSON Web Key
                // exposing the exponent and the modulus parameters.
                var parameters = algorithm.ExportParameters(includePrivateParameters: false);
                Debug.Assert(parameters.Exponent != null, "A null exponent was returned by RSA.ExportParameters()");
                Debug.Assert(parameters.Modulus != null, "A null modulus was returned by RSA.ExportParameters()");

                var key = new JsonWebKey {
                    Use = JsonWebKeyUseNames.Sig,
                    Kty = JsonWebAlgorithmsKeyTypes.RSA,

                    // Resolve the JWA identifier from the algorithm specified in the credentials.
                    Alg = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.SignatureAlgorithm),

                    // Use the key identifier specified
                    // in the signing credentials.
                    Kid = identifier?.LocalId,

                    // Both E and N must be base64url-encoded.
                    // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#appendix-A.1
                    E = Base64UrlEncoder.Encode(parameters.Exponent),
                    N = Base64UrlEncoder.Encode(parameters.Modulus)
                };

                X509Certificate2 x509Certificate = null;

                // Determine whether the signing credentials are directly based on a X.509 certificate.
                var x509SigningCredentials = credentials as X509SigningCredentials;
                if (x509SigningCredentials != null)
                {
                    x509Certificate = x509SigningCredentials.Certificate;
                }

                // Skip looking for a X509SecurityKey in SigningCredentials.SigningKey
                // if a certificate has been found in the SigningCredentials instance.
                if (x509Certificate == null)
                {
                    // Determine whether the security key is an asymmetric key embedded in a X.509 certificate.
                    var x509SecurityKey = credentials.SigningKey as X509SecurityKey;
                    if (x509SecurityKey != null)
                    {
                        x509Certificate = x509SecurityKey.Certificate;
                    }
                }

                // Skip looking for a X509AsymmetricSecurityKey in SigningCredentials.SigningKey
                // if a certificate has been found in SigningCredentials or SigningCredentials.SigningKey.
                if (x509Certificate == null)
                {
                    // Determine whether the security key is an asymmetric key embedded in a X.509 certificate.
                    var x509AsymmetricSecurityKey = credentials.SigningKey as X509AsymmetricSecurityKey;
                    if (x509AsymmetricSecurityKey != null)
                    {
                        // The X.509 certificate is not directly accessible when using X509AsymmetricSecurityKey.
                        // Reflection is the only way to get the certificate used to create the security key.
                        var field = typeof(X509AsymmetricSecurityKey).GetField(
                            name: "certificate",
                            bindingAttr: BindingFlags.Instance | BindingFlags.NonPublic);
                        Debug.Assert(field != null);

                        x509Certificate = (X509Certificate2)field.GetValue(x509AsymmetricSecurityKey);
                    }
                }

                // If the signing key is embedded in a X.509 certificate, set
                // the x5t and x5c parameters using the certificate details.
                if (x509Certificate != null)
                {
                    // x5t must be base64url-encoded.
                    // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#section-4.8
                    key.X5t = Base64UrlEncoder.Encode(x509Certificate.GetCertHash());

                    // Unlike E or N, the certificates contained in x5c
                    // must be base64-encoded and not base64url-encoded.
                    // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#section-4.7
                    key.X5c.Add(Convert.ToBase64String(x509Certificate.RawData));
                }

                notification.Keys.Add(key);
            }

            await Options.Provider.HandleCryptographyRequest(notification);

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

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

            var response = new JObject();
            var keys     = new JArray();

            foreach (var key in notification.Keys)
            {
                var item = new JObject();

                // Ensure a key type has been provided.
                // See http://tools.ietf.org/html/draft-ietf-jose-json-web-key-31#section-4.1
                if (string.IsNullOrEmpty(key.Kty))
                {
                    Options.Logger.LogError("A JSON Web Key was excluded from the key set because " +
                                            "it didn't contain the mandatory 'kid' parameter.");

                    continue;
                }

                // Create a dictionary associating the
                // JsonWebKey components with their values.
                var parameters = new Dictionary <string, string> {
                    { JsonWebKeyParameterNames.Kid, key.Kid },
                    { JsonWebKeyParameterNames.Use, key.Use },
                    { JsonWebKeyParameterNames.Kty, key.Kty },
                    { JsonWebKeyParameterNames.KeyOps, key.KeyOps },
                    { JsonWebKeyParameterNames.Alg, key.Alg },
                    { JsonWebKeyParameterNames.E, key.E },
                    { JsonWebKeyParameterNames.N, key.N },
                    { JsonWebKeyParameterNames.X5t, key.X5t },
                    { JsonWebKeyParameterNames.X5u, key.X5u }
                };

                foreach (var parameter in parameters)
                {
                    if (!string.IsNullOrEmpty(parameter.Value))
                    {
                        item.Add(parameter.Key, parameter.Value);
                    }
                }

                if (key.X5c.Count != 0)
                {
                    item.Add(JsonWebKeyParameterNames.X5c, JArray.FromObject(key.X5c));
                }

                keys.Add(item);
            }

            response.Add(JsonWebKeyParameterNames.Keys, keys);

            return(await SendCryptographyResponseAsync(request, response));
        }
        private async Task <bool> InvokeConfigurationEndpointAsync()
        {
            // Metadata requests must be made via GET.
            // See http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
            if (!string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                Logger.LogError("The configuration request was rejected because an invalid " +
                                "HTTP method was specified: {Method}.", Request.Method);

                return(await SendConfigurationResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The specified HTTP method is not valid."
                }));
            }

            var request = new OpenIdConnectRequest(Request.Query);

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

            // Store the configuration request in the OWIN context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractConfigurationRequestContext(Context, Options, request);
            await Options.Provider.ExtractConfigurationRequest(@event);

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

                return(true);
            }

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

                return(false);
            }

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

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

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

            var context = new ValidateConfigurationRequestContext(Context, Options, request);
            await Options.Provider.ValidateConfigurationRequest(context);

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

                return(true);
            }

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

                return(false);
            }

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

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

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

            var notification = new HandleConfigurationRequestContext(Context, Options, request)
            {
                Issuer = Context.GetIssuer(Options)
            };

            if (Options.AuthorizationEndpointPath.HasValue)
            {
                notification.AuthorizationEndpoint = notification.Issuer.AddPath(Options.AuthorizationEndpointPath);
            }

            if (Options.CryptographyEndpointPath.HasValue)
            {
                notification.CryptographyEndpoint = notification.Issuer.AddPath(Options.CryptographyEndpointPath);
            }

            if (Options.IntrospectionEndpointPath.HasValue)
            {
                notification.IntrospectionEndpoint = notification.Issuer.AddPath(Options.IntrospectionEndpointPath);

                notification.IntrospectionEndpointAuthenticationMethods.Add(
                    OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretBasic);
                notification.IntrospectionEndpointAuthenticationMethods.Add(
                    OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretPost);
            }

            if (Options.LogoutEndpointPath.HasValue)
            {
                notification.LogoutEndpoint = notification.Issuer.AddPath(Options.LogoutEndpointPath);
            }

            if (Options.RevocationEndpointPath.HasValue)
            {
                notification.RevocationEndpoint = notification.Issuer.AddPath(Options.RevocationEndpointPath);

                notification.RevocationEndpointAuthenticationMethods.Add(
                    OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretBasic);
                notification.RevocationEndpointAuthenticationMethods.Add(
                    OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretPost);
            }

            if (Options.TokenEndpointPath.HasValue)
            {
                notification.TokenEndpoint = notification.Issuer.AddPath(Options.TokenEndpointPath);

                notification.TokenEndpointAuthenticationMethods.Add(
                    OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretBasic);
                notification.TokenEndpointAuthenticationMethods.Add(
                    OpenIdConnectConstants.ClientAuthenticationMethods.ClientSecretPost);
            }

            if (Options.UserinfoEndpointPath.HasValue)
            {
                notification.UserinfoEndpoint = notification.Issuer.AddPath(Options.UserinfoEndpointPath);
            }

            if (Options.AuthorizationEndpointPath.HasValue)
            {
                notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Implicit);

                if (Options.TokenEndpointPath.HasValue)
                {
                    // Only expose the code grant type and the code challenge methods
                    // if both the authorization and the token endpoints are enabled.
                    notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.AuthorizationCode);

                    // Note: supporting S256 is mandatory for authorization servers that implement PKCE.
                    // See https://tools.ietf.org/html/rfc7636#section-4.2 for more information.
                    notification.CodeChallengeMethods.Add(OpenIdConnectConstants.CodeChallengeMethods.Plain);
                    notification.CodeChallengeMethods.Add(OpenIdConnectConstants.CodeChallengeMethods.Sha256);
                }
            }

            if (Options.TokenEndpointPath.HasValue)
            {
                notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.RefreshToken);
                notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.ClientCredentials);
                notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Password);
            }

            // Only populate response_modes_supported and response_types_supported
            // if the authorization endpoint is available.
            if (Options.AuthorizationEndpointPath.HasValue)
            {
                notification.ResponseModes.Add(OpenIdConnectConstants.ResponseModes.FormPost);
                notification.ResponseModes.Add(OpenIdConnectConstants.ResponseModes.Fragment);
                notification.ResponseModes.Add(OpenIdConnectConstants.ResponseModes.Query);

                notification.ResponseTypes.Add(OpenIdConnectConstants.ResponseTypes.Token);

                // Only expose response types containing code when
                // the token endpoint has not been explicitly disabled.
                if (Options.TokenEndpointPath.HasValue)
                {
                    notification.ResponseTypes.Add(OpenIdConnectConstants.ResponseTypes.Code);

                    notification.ResponseTypes.Add(
                        OpenIdConnectConstants.ResponseTypes.Code + ' ' +
                        OpenIdConnectConstants.ResponseTypes.Token);
                }

                // Only expose the response types containing id_token if an asymmetric signing key is available.
                if (Options.SigningCredentials.Any(credentials => credentials.Key is AsymmetricSecurityKey))
                {
                    notification.ResponseTypes.Add(OpenIdConnectConstants.ResponseTypes.IdToken);

                    notification.ResponseTypes.Add(
                        OpenIdConnectConstants.ResponseTypes.IdToken + ' ' +
                        OpenIdConnectConstants.ResponseTypes.Token);

                    // Only expose response types containing code when
                    // the token endpoint has not been explicitly disabled.
                    if (Options.TokenEndpointPath.HasValue)
                    {
                        notification.ResponseTypes.Add(
                            OpenIdConnectConstants.ResponseTypes.Code + ' ' +
                            OpenIdConnectConstants.ResponseTypes.IdToken);

                        notification.ResponseTypes.Add(
                            OpenIdConnectConstants.ResponseTypes.Code + ' ' +
                            OpenIdConnectConstants.ResponseTypes.IdToken + ' ' +
                            OpenIdConnectConstants.ResponseTypes.Token);
                    }
                }
            }

            notification.Scopes.Add(OpenIdConnectConstants.Scopes.OpenId);

            notification.Claims.Add(OpenIdConnectConstants.Claims.Audience);
            notification.Claims.Add(OpenIdConnectConstants.Claims.ExpiresAt);
            notification.Claims.Add(OpenIdConnectConstants.Claims.IssuedAt);
            notification.Claims.Add(OpenIdConnectConstants.Claims.Issuer);
            notification.Claims.Add(OpenIdConnectConstants.Claims.JwtId);
            notification.Claims.Add(OpenIdConnectConstants.Claims.Subject);

            notification.SubjectTypes.Add(OpenIdConnectConstants.SubjectTypes.Public);

            foreach (var credentials in Options.SigningCredentials)
            {
                // If the signing key is not an asymmetric key, ignore it.
                if (!(credentials.Key is AsymmetricSecurityKey))
                {
                    continue;
                }

                // Try to resolve the JWA algorithm short name. If a null value is returned, ignore it.
                var algorithm = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.Algorithm);
                if (string.IsNullOrEmpty(algorithm))
                {
                    continue;
                }

                notification.IdTokenSigningAlgorithms.Add(algorithm);
            }

            await Options.Provider.HandleConfigurationRequest(notification);

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

                return(true);
            }

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

                return(false);
            }

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

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

            var response = new OpenIdConnectResponse
            {
                [OpenIdConnectConstants.Metadata.Issuer] = notification.Issuer,
                [OpenIdConnectConstants.Metadata.AuthorizationEndpoint] = notification.AuthorizationEndpoint,
                [OpenIdConnectConstants.Metadata.TokenEndpoint]         = notification.TokenEndpoint,
                [OpenIdConnectConstants.Metadata.IntrospectionEndpoint] = notification.IntrospectionEndpoint,
                [OpenIdConnectConstants.Metadata.EndSessionEndpoint]    = notification.LogoutEndpoint,
                [OpenIdConnectConstants.Metadata.RevocationEndpoint]    = notification.RevocationEndpoint,
                [OpenIdConnectConstants.Metadata.UserinfoEndpoint]      = notification.UserinfoEndpoint,
                [OpenIdConnectConstants.Metadata.JwksUri]                                   = notification.CryptographyEndpoint,
                [OpenIdConnectConstants.Metadata.GrantTypesSupported]                       = new JArray(notification.GrantTypes),
                [OpenIdConnectConstants.Metadata.ResponseTypesSupported]                    = new JArray(notification.ResponseTypes),
                [OpenIdConnectConstants.Metadata.ResponseModesSupported]                    = new JArray(notification.ResponseModes),
                [OpenIdConnectConstants.Metadata.ScopesSupported]                           = new JArray(notification.Scopes),
                [OpenIdConnectConstants.Metadata.ClaimsSupported]                           = new JArray(notification.Claims),
                [OpenIdConnectConstants.Metadata.IdTokenSigningAlgValuesSupported]          = new JArray(notification.IdTokenSigningAlgorithms),
                [OpenIdConnectConstants.Metadata.CodeChallengeMethodsSupported]             = new JArray(notification.CodeChallengeMethods),
                [OpenIdConnectConstants.Metadata.SubjectTypesSupported]                     = new JArray(notification.SubjectTypes),
                [OpenIdConnectConstants.Metadata.TokenEndpointAuthMethodsSupported]         = new JArray(notification.TokenEndpointAuthenticationMethods),
                [OpenIdConnectConstants.Metadata.IntrospectionEndpointAuthMethodsSupported] = new JArray(notification.IntrospectionEndpointAuthenticationMethods),
                [OpenIdConnectConstants.Metadata.RevocationEndpointAuthMethodsSupported]    = new JArray(notification.RevocationEndpointAuthenticationMethods)
            };

            foreach (var metadata in notification.Metadata)
            {
                response.SetParameter(metadata.Key, metadata.Value);
            }

            return(await SendConfigurationResponseAsync(response));
        }
        /// <summary>
        /// Authorization Server middleware component which is added to an OWIN pipeline. This constructor is not
        /// called by application code directly, instead it is added by calling the the IAppBuilder UseOpenIdConnectServer
        /// extension method.
        /// </summary>
        public OpenIdConnectServerMiddleware(OwinMiddleware next, IAppBuilder app, OpenIdConnectServerOptions options)
            : base(next, options)
        {
            if (Options.HtmlEncoder == null)
            {
                throw new ArgumentException("The HTML encoder registered in the options " +
                                            "cannot be null.", nameof(options));
            }

            if (Options.Provider == null)
            {
                throw new ArgumentException("The authorization provider registered in " +
                                            "the options cannot be null.", nameof(options));
            }

            if (Options.SystemClock == null)
            {
                throw new ArgumentException("The system clock registered in the options " +
                                            "cannot be null.", nameof(options));
            }

            if (Options.Issuer != null)
            {
                if (!Options.Issuer.IsAbsoluteUri)
                {
                    throw new ArgumentException("The issuer registered in the options must be " +
                                                "a valid absolute URI.", nameof(options));
                }

                // See http://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery
                if (!string.IsNullOrEmpty(Options.Issuer.Query) || !string.IsNullOrEmpty(Options.Issuer.Fragment))
                {
                    throw new ArgumentException("The issuer registered in the options must contain no query " +
                                                "and no fragment parts.", nameof(options));
                }

                // Note: while the issuer parameter should be a HTTPS URI, making HTTPS mandatory
                // in Owin.Security.OpenIdConnect.Server would prevent the end developer from
                // running the different samples in test environments, where HTTPS is often disabled.
                // To mitigate this issue, AllowInsecureHttp can be set to true to bypass the HTTPS check.
                // See http://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery
                if (!Options.AllowInsecureHttp && string.Equals(Options.Issuer.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase))
                {
                    throw new ArgumentException("The issuer registered in the options must be a HTTPS URI when " +
                                                "AllowInsecureHttp is not set to true.", nameof(options));
                }
            }

            if (Options.AccessTokenHandler != null && !(Options.AccessTokenHandler is ISecurityTokenValidator))
            {
                throw new ArgumentException("The access token handler must implement 'ISecurityTokenValidator'.", nameof(options));
            }

            if (Options.Logger == null)
            {
                Options.Logger = new LoggerFactory().CreateLogger <OpenIdConnectServerMiddleware>();
            }

            if (Options.DataProtectionProvider == null)
            {
                // Try to use the application name provided by
                // the OWIN host as the application discriminator.
                var discriminator = new AppProperties(app.Properties).AppName;

                // When an application discriminator cannot be resolved from
                // the OWIN host properties, generate a temporary identifier.
                if (string.IsNullOrEmpty(discriminator))
                {
                    discriminator = Guid.NewGuid().ToString();
                }

                Options.DataProtectionProvider = DataProtectionProvider.Create(discriminator);
            }

            if (Options.AccessTokenFormat == null)
            {
                var protector = Options.DataProtectionProvider.CreateProtector(
                    nameof(OpenIdConnectServerMiddleware),
                    Options.AuthenticationType, "Access_Token", "v1");

                Options.AccessTokenFormat = new AspNetTicketDataFormat(new DataProtectorShim(protector));
            }

            if (Options.AuthorizationCodeFormat == null)
            {
                var protector = Options.DataProtectionProvider.CreateProtector(
                    nameof(OpenIdConnectServerMiddleware),
                    Options.AuthenticationType, "Authorization_Code", "v1");

                Options.AuthorizationCodeFormat = new AspNetTicketDataFormat(new DataProtectorShim(protector));
            }

            if (Options.RefreshTokenFormat == null)
            {
                var protector = Options.DataProtectionProvider.CreateProtector(
                    nameof(OpenIdConnectServerMiddleware),
                    Options.AuthenticationType, "Refresh_Token", "v1");

                Options.RefreshTokenFormat = new AspNetTicketDataFormat(new DataProtectorShim(protector));
            }

            var environment = new AppProperties(app.Properties).Get <string>("host.AppMode");

            if (Options.AllowInsecureHttp && !string.Equals(environment, "Development", StringComparison.OrdinalIgnoreCase))
            {
                Options.Logger.LogWarning("Disabling the transport security requirement is not recommended on production. " +
                                          "Consider setting 'OpenIdConnectServerOptions.AllowInsecureHttp' to 'false' " +
                                          "to prevent the OpenID Connect server middleware from serving non-HTTPS requests.");
            }

            // If no key has been explicitly added, use the fallback mode.
            if (Options.EncryptingCredentials.Count == 0 || Options.SigningCredentials.Count == 0)
            {
                // When detecting a non-development environment, log a warning to inform the developer
                // that registering a X.509 certificate is recommended when going live.
                if (!string.Equals(environment, "Development", StringComparison.OrdinalIgnoreCase))
                {
                    Options.Logger.LogWarning("No explicit signing credentials have been registered. " +
                                              "Using a X.509 certificate stored in the machine store " +
                                              "is recommended for production environments.");
                }

                var directory = OpenIdConnectServerHelpers.GetDefaultKeyStorageDirectory();
                if (directory == null)
                {
                    Options.Logger.LogError("No suitable directory can be found to store the dynamically-generated RSA keys.");

                    return;
                }

                // Get a data protector from the services provider.
                var protector = Options.DataProtectionProvider.CreateProtector(
                    nameof(OpenIdConnectServerMiddleware),
                    Options.AuthenticationType, "Keys", "v1");

                // Note: all the exceptions thrown when enumerating
                // existing keys or generating new ones must be caught.
                try {
                    foreach (var key in OpenIdConnectServerHelpers.GetKeys(directory))
                    {
                        // Extract the key material using the data protector.
                        // Ignore the key if the decryption process failed.
                        string usage;
                        var    parameters = OpenIdConnectServerHelpers.DecryptKey(protector, key.Value, out usage);
                        if (parameters == null)
                        {
                            Options.Logger.LogDebug("An invalid/incompatible key was ignored: {Key}.", key.Key);

                            continue;
                        }

                        if (string.Equals(usage, "Encryption", StringComparison.OrdinalIgnoreCase))
                        {
                            var algorithm = RSA.Create();
                            algorithm.ImportParameters(parameters.Value);

                            // Add the key to the encryption credentials list.
                            Options.EncryptingCredentials.AddKey(new RsaSecurityKey(algorithm));

                            Options.Logger.LogInformation("An existing key was automatically added to the " +
                                                          "encryption credentials list: {Key}.", key.Key);
                        }

                        else if (string.Equals(usage, "Signing", StringComparison.OrdinalIgnoreCase))
                        {
                            var algorithm = RSA.Create();
                            algorithm.ImportParameters(parameters.Value);

                            // Add the key to the signing credentials list.
                            Options.SigningCredentials.AddKey(new RsaSecurityKey(algorithm));

                            Options.Logger.LogInformation("An existing key was automatically added to the " +
                                                          "signing credentials list: {Key}.", key.Key);
                        }
                    }
                }

                catch (Exception exception) {
                    Options.Logger.LogDebug("An error ocurred when retrieving the keys stored " +
                                            "on the disk: {Exception}.", exception.ToString());
                }

                // Note: all the exceptions thrown when enumerating
                // existing keys or generating new ones must be caught.
                try {
                    // If no encryption key has been found, generate and persist a new RSA key.
                    if (Options.EncryptingCredentials.Count == 0)
                    {
                        // Generate a new RSA key and export its public/private parameters.
                        var algorithm  = OpenIdConnectServerHelpers.GenerateKey(size: 2048);
                        var parameters = algorithm.ExportParameters(/* includePrivateParameters: */ true);

                        // Add the encryption key to the credentials list before trying to persist it on the disk.
                        Options.EncryptingCredentials.AddKey(new RsaSecurityKey(algorithm));

                        try {
                            // Encrypt the key using the data protector and try to persist it on the disk.
                            var key  = OpenIdConnectServerHelpers.EncryptKey(protector, parameters, usage: "Encryption");
                            var path = OpenIdConnectServerHelpers.PersistKey(directory, key);

                            Options.Logger.LogInformation("A new RSA key was automatically generated, added to the " +
                                                          "encryption credentials list and persisted on the disk: {Path}.", path);
                        }

                        catch (Exception exception) {
                            Options.Logger.LogWarning("An error ocurred when persisting a new key on the disk: {Exception}. " +
                                                      "The key will be kept in memory and will be lost when the " +
                                                      "application shuts down or restarts.", exception.ToString());
                        }
                    }

                    // If no signing key has been found, generate and persist a new RSA key.
                    if (Options.SigningCredentials.Count == 0)
                    {
                        // Generate a new RSA key and export its public/private parameters.
                        var algorithm  = OpenIdConnectServerHelpers.GenerateKey(size: 2048);
                        var parameters = algorithm.ExportParameters(/* includePrivateParameters: */ true);

                        // Add the signing key to the credentials list before trying to persist it on the disk.
                        Options.SigningCredentials.AddKey(new RsaSecurityKey(algorithm));

                        try {
                            // Encrypt the key using the data protector and try to persist it on the disk.
                            var key  = OpenIdConnectServerHelpers.EncryptKey(protector, parameters, usage: "Signing");
                            var path = OpenIdConnectServerHelpers.PersistKey(directory, key);

                            Options.Logger.LogInformation("A new RSA key was automatically generated, added to the " +
                                                          "signing credentials list and persisted on the disk: {Path}.", path);
                        }

                        catch (Exception exception) {
                            Options.Logger.LogWarning("An error ocurred when persisting a new key on the disk: {Exception}. " +
                                                      "The key will be kept in memory and will be lost when the " +
                                                      "application shuts down or restarts.", exception.ToString());
                        }
                    }
                }

                catch (Exception exception) {
                    Options.Logger.LogDebug("An error ocurred when generating a new key: {Exception}.", exception.ToString());
                }
            }

            if (Options.SigningCredentials.Count == 0)
            {
                throw new ArgumentException("At least one signing key must be registered.", nameof(options));
            }
        }
        private async Task <string> SerializeIdentityTokenAsync(
            ClaimsIdentity identity, AuthenticationProperties properties,
            OpenIdConnectRequest request, OpenIdConnectResponse response)
        {
            // Replace the identity by a new one containing only the filtered claims.
            // Actors identities are also filtered (delegation scenarios).
            identity = identity.Clone(claim =>
            {
                // Never exclude the subject claim.
                if (string.Equals(claim.Type, OpenIdConnectConstants.Claims.Subject, StringComparison.OrdinalIgnoreCase))
                {
                    return(true);
                }

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

                    return(false);
                }

                return(true);
            });

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

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

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

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

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

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

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

            ticket.SetAudiences(ticket.GetPresenters());

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

            await Options.Provider.SerializeIdentityToken(notification);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            return(token);
        }
        private async Task <bool> InvokeConfigurationEndpointAsync()
        {
            // Metadata requests must be made via GET.
            // See http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
            if (!string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                Options.Logger.LogError("The discovery request was rejected because an invalid " +
                                        "HTTP method was used: {Method}.", Request.Method);

                return(await SendConfigurationResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "Invalid HTTP method: make sure to use GET."
                }));
            }

            var request = new OpenIdConnectRequest(Request.Query);

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

            // Store the discovery request in the OWIN context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractConfigurationRequestContext(Context, Options, request);
            await Options.Provider.ExtractConfigurationRequest(@event);

            if (@event.HandledResponse)
            {
                return(true);
            }

            else if (@event.Skipped)
            {
                return(false);
            }

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

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

            var context = new ValidateConfigurationRequestContext(Context, Options, request);
            await Options.Provider.ValidateConfigurationRequest(context);

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

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

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

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

            var notification = new HandleConfigurationRequestContext(Context, Options, request);

            notification.Issuer = Context.GetIssuer(Options);

            if (Options.AuthorizationEndpointPath.HasValue)
            {
                notification.AuthorizationEndpoint = notification.Issuer.AddPath(Options.AuthorizationEndpointPath);
            }

            if (Options.CryptographyEndpointPath.HasValue)
            {
                notification.CryptographyEndpoint = notification.Issuer.AddPath(Options.CryptographyEndpointPath);
            }

            if (Options.IntrospectionEndpointPath.HasValue)
            {
                notification.IntrospectionEndpoint = notification.Issuer.AddPath(Options.IntrospectionEndpointPath);
            }

            if (Options.LogoutEndpointPath.HasValue)
            {
                notification.LogoutEndpoint = notification.Issuer.AddPath(Options.LogoutEndpointPath);
            }

            if (Options.RevocationEndpointPath.HasValue)
            {
                notification.RevocationEndpoint = notification.Issuer.AddPath(Options.RevocationEndpointPath);
            }

            if (Options.TokenEndpointPath.HasValue)
            {
                notification.TokenEndpoint = notification.Issuer.AddPath(Options.TokenEndpointPath);
            }

            if (Options.UserinfoEndpointPath.HasValue)
            {
                notification.UserinfoEndpoint = notification.Issuer.AddPath(Options.UserinfoEndpointPath);
            }

            if (Options.AuthorizationEndpointPath.HasValue)
            {
                // Only expose the implicit grant type if the token
                // endpoint has not been explicitly disabled.
                notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Implicit);

                if (Options.TokenEndpointPath.HasValue)
                {
                    // Only expose the authorization code and refresh token grant types
                    // if both the authorization and the token endpoints are enabled.
                    notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.AuthorizationCode);
                }
            }

            if (Options.TokenEndpointPath.HasValue)
            {
                notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.RefreshToken);
                notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.ClientCredentials);
                notification.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.Password);
            }

            // Only populate response_modes_supported and response_types_supported
            // if the authorization endpoint is available.
            if (Options.AuthorizationEndpointPath.HasValue)
            {
                notification.ResponseModes.Add(OpenIdConnectConstants.ResponseModes.FormPost);
                notification.ResponseModes.Add(OpenIdConnectConstants.ResponseModes.Fragment);
                notification.ResponseModes.Add(OpenIdConnectConstants.ResponseModes.Query);

                notification.ResponseTypes.Add(OpenIdConnectConstants.ResponseTypes.Token);
                notification.ResponseTypes.Add(OpenIdConnectConstants.ResponseTypes.IdToken);

                notification.ResponseTypes.Add(
                    OpenIdConnectConstants.ResponseTypes.IdToken + ' ' +
                    OpenIdConnectConstants.ResponseTypes.Token);

                // Only expose response types containing code when
                // the token endpoint has not been explicitly disabled.
                if (Options.TokenEndpointPath.HasValue)
                {
                    notification.ResponseTypes.Add(OpenIdConnectConstants.ResponseTypes.Code);

                    notification.ResponseTypes.Add(
                        OpenIdConnectConstants.ResponseTypes.Code + ' ' +
                        OpenIdConnectConstants.ResponseTypes.Token);

                    notification.ResponseTypes.Add(
                        OpenIdConnectConstants.ResponseTypes.Code + ' ' +
                        OpenIdConnectConstants.ResponseTypes.IdToken);

                    notification.ResponseTypes.Add(
                        OpenIdConnectConstants.ResponseTypes.Code + ' ' +
                        OpenIdConnectConstants.ResponseTypes.IdToken + ' ' +
                        OpenIdConnectConstants.ResponseTypes.Token);
                }
            }

            notification.Scopes.Add(OpenIdConnectConstants.Scopes.OpenId);

            notification.SubjectTypes.Add(OpenIdConnectConstants.SubjectTypes.Public);

            // Note: supporting S256 is mandatory for authorization servers that implement PKCE.
            // See https://tools.ietf.org/html/rfc7636#section-4.2 for more information.
            notification.CodeChallengeMethods.Add(OpenIdConnectConstants.CodeChallengeMethods.Plain);
            notification.CodeChallengeMethods.Add(OpenIdConnectConstants.CodeChallengeMethods.Sha256);

            foreach (var credentials in Options.SigningCredentials)
            {
                // Try to resolve the JWA algorithm short name. If a null value is returned, ignore it.
                var algorithm = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.SignatureAlgorithm);
                if (string.IsNullOrEmpty(algorithm))
                {
                    continue;
                }

                // If the algorithm is already listed, ignore it.
                if (notification.SigningAlgorithms.Contains(algorithm))
                {
                    continue;
                }

                notification.SigningAlgorithms.Add(algorithm);
            }

            await Options.Provider.HandleConfigurationRequest(notification);

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

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

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

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

            return(await SendConfigurationResponseAsync(new OpenIdConnectResponse(notification.Metadata)));
        }
        /// <summary>
        /// Authorization Server middleware component which is added to an OWIN pipeline. This constructor is not
        /// called by application code directly, instead it is added by calling the the IAppBuilder UseOpenIdConnectServer
        /// extension method.
        /// </summary>
        public OpenIdConnectServerMiddleware(OwinMiddleware next, IAppBuilder app, OpenIdConnectServerOptions options)
            : base(next, options)
        {
            if (Options.HtmlEncoder == null)
            {
                throw new ArgumentException("The HTML encoder registered in the options " +
                                            "cannot be null.", nameof(options));
            }

            if (Options.Provider == null)
            {
                throw new ArgumentException("The authorization provider registered in " +
                                            "the options cannot be null.", nameof(options));
            }

            if (Options.RandomNumberGenerator == null)
            {
                throw new ArgumentException("The random number generator registered in " +
                                            "the options cannot be null.", nameof(options));
            }

            if (Options.SystemClock == null)
            {
                throw new ArgumentException("The system clock registered in the options " +
                                            "cannot be null.", nameof(options));
            }

            if (Options.Issuer != null)
            {
                if (!Options.Issuer.IsAbsoluteUri)
                {
                    throw new ArgumentException("The issuer registered in the options must be " +
                                                "a valid absolute URI.", nameof(options));
                }

                // See http://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery
                if (!string.IsNullOrEmpty(Options.Issuer.Query) || !string.IsNullOrEmpty(Options.Issuer.Fragment))
                {
                    throw new ArgumentException("The issuer registered in the options must contain no query " +
                                                "and no fragment parts.", nameof(options));
                }

                // Note: while the issuer parameter should be a HTTPS URI, making HTTPS mandatory
                // in Owin.Security.OpenIdConnect.Server would prevent the end developer from
                // running the different samples in test environments, where HTTPS is often disabled.
                // To mitigate this issue, AllowInsecureHttp can be set to true to bypass the HTTPS check.
                // See http://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery
                if (!Options.AllowInsecureHttp && string.Equals(Options.Issuer.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase))
                {
                    throw new ArgumentException("The issuer registered in the options must be a HTTPS URI when " +
                                                "AllowInsecureHttp is not set to true.", nameof(options));
                }
            }

            if (Options.Logger == null)
            {
                Options.Logger = new LoggerFactory().CreateLogger <OpenIdConnectServerMiddleware>();
            }

            if (Options.DataProtectionProvider == null)
            {
                // Create a new DI container and register
                // the data protection services.
                var services = new ServiceCollection();

                services.AddDataProtection(configuration => {
                    // Try to use the application name provided by
                    // the OWIN host as the application discriminator.
                    var discriminator = new AppProperties(app.Properties).AppName;

                    // When an application discriminator cannot be resolved from
                    // the OWIN host properties, generate a temporary identifier.
                    if (string.IsNullOrEmpty(discriminator))
                    {
                        discriminator = Guid.NewGuid().ToString();
                    }

                    configuration.ApplicationDiscriminator = discriminator;
                });

                var container = services.BuildServiceProvider();

                // Resolve a data protection provider from the services container.
                Options.DataProtectionProvider = container.GetRequiredService <IDataProtectionProvider>();
            }

            if (Options.AccessTokenFormat == null)
            {
                var protector = Options.DataProtectionProvider.CreateProtector(
                    nameof(OpenIdConnectServerMiddleware),
                    Options.AuthenticationType, "Access_Token", "v1");

                Options.AccessTokenFormat = new AspNetTicketDataFormat(new DataProtectorShim(protector));
            }

            if (Options.AuthorizationCodeFormat == null)
            {
                var protector = Options.DataProtectionProvider.CreateProtector(
                    nameof(OpenIdConnectServerMiddleware),
                    Options.AuthenticationType, "Authorization_Code", "v1");

                Options.AuthorizationCodeFormat = new AspNetTicketDataFormat(new DataProtectorShim(protector));
            }

            if (Options.RefreshTokenFormat == null)
            {
                var protector = Options.DataProtectionProvider.CreateProtector(
                    nameof(OpenIdConnectServerMiddleware),
                    Options.AuthenticationType, "Refresh_Token", "v1");

                Options.RefreshTokenFormat = new AspNetTicketDataFormat(new DataProtectorShim(protector));
            }

            // If no key has been explicitly added, use the fallback mode.
            if (Options.EncryptingCredentials.Count == 0 || Options.SigningCredentials.Count == 0)
            {
                var directory = OpenIdConnectServerHelpers.GetDefaultKeyStorageDirectory();
                Debug.Assert(directory != null);

                // Get a data protector from the services provider.
                var protector = Options.DataProtectionProvider.CreateProtector(
                    nameof(OpenIdConnectServerMiddleware),
                    Options.AuthenticationType, "Keys", "v1");

                foreach (var file in directory.EnumerateFiles("*.key"))
                {
                    using (var buffer = new MemoryStream())
                        using (var stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) {
                            // Copy the key content to the buffer.
                            stream.CopyTo(buffer);

                            // Extract the key material using the data protector.
                            // Ignore the key if the decryption process failed.
                            string usage;
                            var    parameters = OpenIdConnectServerHelpers.DecryptKey(protector, buffer.ToArray(), out usage);
                            if (parameters == null)
                            {
                                Options.Logger.LogDebug("An invalid/incompatible key was ignored: {Key}.", file.FullName);

                                continue;
                            }

                            if (string.Equals(usage, "Encryption", StringComparison.OrdinalIgnoreCase))
                            {
                                // Only add the encryption key if none has been explictly added.
                                if (Options.EncryptingCredentials.Count != 0)
                                {
                                    continue;
                                }

                                var algorithm = RSA.Create();
                                algorithm.ImportParameters(parameters.Value);

                                // Add the key to the encryption credentials list.
                                Options.EncryptingCredentials.AddKey(new RsaSecurityKey(algorithm));

                                Options.Logger.LogInformation("An existing key was automatically added to the " +
                                                              "encryption credentials list: {Key}.", file.FullName);
                            }

                            else if (string.Equals(usage, "Signing", StringComparison.OrdinalIgnoreCase))
                            {
                                // Only add the signing key if none has been explictly added.
                                if (Options.SigningCredentials.Count != 0)
                                {
                                    continue;
                                }

                                var algorithm = RSA.Create();
                                algorithm.ImportParameters(parameters.Value);

                                // Add the key to the signing credentials list.
                                Options.SigningCredentials.AddKey(new RsaSecurityKey(algorithm));

                                Options.Logger.LogInformation("An existing key was automatically added to the " +
                                                              "signing credentials list: {Key}.", file.FullName);
                            }
                        }
                }

                // If no encryption key has been found, generate and persist a new RSA key.
                if (Options.EncryptingCredentials.Count == 0)
                {
                    // Generate a new RSA key and export its public/private parameters.
                    var algorithm  = OpenIdConnectServerHelpers.GenerateKey(size: 2048);
                    var parameters = algorithm.ExportParameters(/* includePrivateParameters: */ true);

                    // Generate a new file name for the key and determine its absolute path.
                    var path = Path.Combine(directory.FullName, Guid.NewGuid().ToString() + ".key");

                    using (var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write)) {
                        // Encrypt the key using the data protector.
                        var bytes = OpenIdConnectServerHelpers.EncryptKey(protector, parameters, usage: "Encryption");

                        // Write the encrypted key to the file stream.
                        stream.Write(bytes, 0, bytes.Length);
                    }

                    Options.EncryptingCredentials.AddKey(new RsaSecurityKey(algorithm));

                    Options.Logger.LogInformation("A new RSA key was automatically generated, added to the " +
                                                  "encryption credentials list and persisted on the disk: {Path}.", path);
                }

                // If no signing key has been found, generate and persist a new RSA key.
                if (Options.SigningCredentials.Count == 0)
                {
                    // Generate a new RSA key and export its public/private parameters.
                    var algorithm  = OpenIdConnectServerHelpers.GenerateKey(size: 2048);
                    var parameters = algorithm.ExportParameters(/* includePrivateParameters: */ true);

                    // Generate a new file name for the key and determine its absolute path.
                    var path = Path.Combine(directory.FullName, Guid.NewGuid().ToString() + ".key");

                    using (var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write)) {
                        // Encrypt the key using the data protector.
                        var bytes = OpenIdConnectServerHelpers.EncryptKey(protector, parameters, usage: "Signing");

                        // Write the encrypted key to the file stream.
                        stream.Write(bytes, 0, bytes.Length);
                    }

                    Options.SigningCredentials.AddKey(new RsaSecurityKey(algorithm));

                    Options.Logger.LogInformation("A new RSA key was automatically generated, added to the " +
                                                  "signing credentials list and persisted on the disk: {Path}.", path);
                }
            }
        }
        private async Task <bool> InvokeCryptographyEndpointAsync()
        {
            // Metadata requests must be made via GET.
            // See http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
            if (!string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                Logger.LogError("The discovery request was rejected because an invalid " +
                                "HTTP method was used: {Method}.", Request.Method);

                return(await SendCryptographyResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "Invalid HTTP method: make sure to use GET."
                }));
            }

            var request = new OpenIdConnectRequest(Request.Query);

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

            // Store the discovery request in the OWIN context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractCryptographyRequestContext(Context, Options, request);
            await Options.Provider.ExtractCryptographyRequest(@event);

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

                return(true);
            }

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

                return(false);
            }

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

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

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

            var context = new ValidateCryptographyRequestContext(Context, Options, request);
            await Options.Provider.ValidateCryptographyRequest(context);

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

                return(true);
            }

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

                return(false);
            }

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

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

            var notification = new HandleCryptographyRequestContext(Context, Options, request);

            foreach (var credentials in Options.SigningCredentials)
            {
                // If the signing key is not an asymmetric key, ignore it.
                if (!(credentials.SigningKey is AsymmetricSecurityKey))
                {
                    continue;
                }

                if (!credentials.SigningKey.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256Signature))
                {
                    Logger.LogInformation("An unsupported signing key was ignored and excluded from the " +
                                          "key set: {Type}. Only RSA asymmetric security keys can be exposed " +
                                          "via the JWKS endpoint.", credentials.SigningKey.GetType().Name);

                    continue;
                }

                // Try to extract a key identifier from the credentials.
                LocalIdKeyIdentifierClause identifier = null;
                credentials.SigningKeyIdentifier?.TryFind(out identifier);

                // Resolve the underlying algorithm from the security key.
                var algorithm = ((AsymmetricSecurityKey)credentials.SigningKey)
                                .GetAsymmetricAlgorithm(
                    algorithm: SecurityAlgorithms.RsaSha256Signature,
                    privateKey: false) as RSA;

                // Skip the key if an algorithm instance cannot be extracted.
                if (algorithm == null)
                {
                    Logger.LogWarning("A signing key was ignored because it was unable " +
                                      "to provide the requested algorithm instance.");

                    continue;
                }

                // Export the RSA public key to create a new JSON Web Key
                // exposing the exponent and the modulus parameters.
                var parameters = algorithm.ExportParameters(includePrivateParameters: false);

                Debug.Assert(parameters.Exponent != null &&
                             parameters.Modulus != null,
                             "RSA.ExportParameters() shouldn't return null parameters.");

                var key = new JsonWebKey
                {
                    Use = JsonWebKeyUseNames.Sig,
                    Kty = JsonWebAlgorithmsKeyTypes.RSA,

                    // Resolve the JWA identifier from the algorithm specified in the credentials.
                    Alg = OpenIdConnectServerHelpers.GetJwtAlgorithm(credentials.SignatureAlgorithm),

                    // Use the key identifier specified
                    // in the signing credentials.
                    Kid = identifier?.LocalId,

                    // Note: both E and N must be base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7518#section-6.2.1.2
                    E = Base64UrlEncoder.Encode(parameters.Exponent),
                    N = Base64UrlEncoder.Encode(parameters.Modulus)
                };

                X509Certificate2 certificate = null;

                // Determine whether the signing credentials are directly based on a X.509 certificate.
                var x509SigningCredentials = credentials as X509SigningCredentials;
                if (x509SigningCredentials != null)
                {
                    certificate = x509SigningCredentials.Certificate;
                }

                // Skip looking for a X509SecurityKey in SigningCredentials.SigningKey
                // if a certificate has been found in the SigningCredentials instance.
                if (certificate == null)
                {
                    // Determine whether the security key is an asymmetric key embedded in a X.509 certificate.
                    var x509SecurityKey = credentials.SigningKey as X509SecurityKey;
                    if (x509SecurityKey != null)
                    {
                        certificate = x509SecurityKey.Certificate;
                    }
                }

                // Skip looking for a X509AsymmetricSecurityKey in SigningCredentials.SigningKey
                // if a certificate has been found in SigningCredentials or SigningCredentials.SigningKey.
                if (certificate == null)
                {
                    // Determine whether the security key is an asymmetric key embedded in a X.509 certificate.
                    var x509AsymmetricSecurityKey = credentials.SigningKey as X509AsymmetricSecurityKey;
                    if (x509AsymmetricSecurityKey != null)
                    {
                        // The X.509 certificate is not directly accessible when using X509AsymmetricSecurityKey.
                        // Reflection is the only way to get the certificate used to create the security key.
                        var field = typeof(X509AsymmetricSecurityKey).GetField(
                            name: "certificate",
                            bindingAttr: BindingFlags.Instance | BindingFlags.NonPublic);
                        Debug.Assert(field != null);

                        certificate = (X509Certificate2)field.GetValue(x509AsymmetricSecurityKey);
                    }
                }

                // If the signing key is embedded in a X.509 certificate, set
                // the x5t and x5c parameters using the certificate details.
                if (certificate != null)
                {
                    // x5t must be base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7517#section-4.8
                    key.X5t = Base64UrlEncoder.Encode(certificate.GetCertHash());

                    // Unlike E or N, the certificates contained in x5c
                    // must be base64-encoded and not base64url-encoded.
                    // See https://tools.ietf.org/html/rfc7517#section-4.7
                    key.X5c.Add(Convert.ToBase64String(certificate.RawData));
                }

                notification.Keys.Add(key);
            }

            await Options.Provider.HandleCryptographyRequest(notification);

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

                return(true);
            }

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

                return(false);
            }

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

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

            var keys = new JArray();

            foreach (var key in notification.Keys)
            {
                var item = new JObject();

                // Ensure a key type has been provided.
                // See https://tools.ietf.org/html/rfc7517#section-4.1
                if (string.IsNullOrEmpty(key.Kty))
                {
                    Logger.LogError("A JSON Web Key was excluded from the key set because " +
                                    "it didn't contain the mandatory 'kid' parameter.");

                    continue;
                }

                // Create a dictionary associating the
                // JsonWebKey components with their values.
                var parameters = new Dictionary <string, string>
                {
                    [JsonWebKeyParameterNames.Kid]    = key.Kid,
                    [JsonWebKeyParameterNames.Use]    = key.Use,
                    [JsonWebKeyParameterNames.Kty]    = key.Kty,
                    [JsonWebKeyParameterNames.KeyOps] = key.KeyOps,
                    [JsonWebKeyParameterNames.Alg]    = key.Alg,
                    [JsonWebKeyParameterNames.E]      = key.E,
                    [JsonWebKeyParameterNames.N]      = key.N,
                    [JsonWebKeyParameterNames.X5t]    = key.X5t,
                    [JsonWebKeyParameterNames.X5u]    = key.X5u
                };

                foreach (var parameter in parameters)
                {
                    if (!string.IsNullOrEmpty(parameter.Value))
                    {
                        item.Add(parameter.Key, parameter.Value);
                    }
                }

                if (key.X5c.Count != 0)
                {
                    item.Add(JsonWebKeyParameterNames.X5c, new JArray(key.X5c));
                }

                keys.Add(item);
            }

            return(await SendCryptographyResponseAsync(new OpenIdConnectResponse
            {
                [OpenIdConnectConstants.Parameters.Keys] = keys
            }));
        }