/// <summary>
 /// Represents an event called for each request to the authorization endpoint
 /// to determine if the request is valid and should continue.
 /// </summary>
 /// <param name="context">The context instance associated with this event.</param>
 /// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns>
 public virtual Task ValidateAuthorizationRequest(ValidateAuthorizationRequestContext context)
 => OnValidateAuthorizationRequest(context);
        private async Task <bool> InvokeAuthorizationEndpointAsync()
        {
            OpenIdConnectRequest request;

            if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                request = new OpenIdConnectRequest(Request.Query);
            }

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

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

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

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

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

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

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

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

            var @event = new ExtractAuthorizationRequestContext(Context, Options, request);
            await Options.Provider.ExtractAuthorizationRequest(@event);

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

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

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

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

            // client_id is mandatory parameter and MUST cause an error when missing.
            // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
            if (string.IsNullOrEmpty(request.ClientId))
            {
                Options.Logger.LogError("The authorization request was rejected because " +
                                        "the mandatory 'client_id' parameter was missing.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "client_id was missing"
                }));
            }

            // While redirect_uri was not mandatory in OAuth2, this parameter
            // is now declared as REQUIRED and MUST cause an error when missing.
            // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
            // To keep AspNet.Security.OpenIdConnect.Server compatible with pure OAuth2 clients,
            // an error is only returned if the request was made by an OpenID Connect client.
            if (string.IsNullOrEmpty(request.RedirectUri) && request.HasScope(OpenIdConnectConstants.Scopes.OpenId))
            {
                Options.Logger.LogError("The authorization request was rejected because " +
                                        "the mandatory 'redirect_uri' parameter was missing.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "redirect_uri must be included when making an OpenID Connect request"
                }));
            }

            if (!string.IsNullOrEmpty(request.RedirectUri))
            {
                // Note: when specified, redirect_uri MUST be an absolute URI.
                // See http://tools.ietf.org/html/rfc6749#section-3.1.2
                // and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
                Uri uri;
                if (!Uri.TryCreate(request.RedirectUri, UriKind.Absolute, out uri))
                {
                    Options.Logger.LogError("The authorization request was rejected because the 'redirect_uri' parameter " +
                                            "didn't correspond to a valid absolute URL: {RedirectUri}.", request.RedirectUri);

                    return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "redirect_uri must be absolute"
                    }));
                }

                // Note: when specified, redirect_uri MUST NOT include a fragment component.
                // See http://tools.ietf.org/html/rfc6749#section-3.1.2
                // and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
                else if (!string.IsNullOrEmpty(uri.Fragment))
                {
                    Options.Logger.LogError("The authorization request was rejected because the 'redirect_uri' " +
                                            "contained a URL fragment: {RedirectUri}.", request.RedirectUri);

                    return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "redirect_uri must not include a fragment"
                    }));
                }
            }

            // Reject requests missing the mandatory response_type parameter.
            if (string.IsNullOrEmpty(request.ResponseType))
            {
                Options.Logger.LogError("The authorization request was rejected because " +
                                        "the mandatory 'response_type' parameter was missing.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "response_type parameter missing"
                }));
            }

            // response_mode=query (explicit or not) and a response_type containing id_token
            // or token are not considered as a safe combination and MUST be rejected.
            // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security
            else if (request.IsQueryResponseMode() && (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) ||
                                                       request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token)))
            {
                Options.Logger.LogError("The authorization request was rejected because the 'response_type'/'response_mode' combination " +
                                        "was unsafe: {ResponseType} ; {ResponseMode}.", request.ResponseType, request.ResponseMode);

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "response_type/response_mode combination unsupported"
                }));
            }

            // Reject OpenID Connect implicit/hybrid requests missing the mandatory nonce parameter.
            // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest,
            // http://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters
            // and http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken.
            else if (string.IsNullOrEmpty(request.Nonce) && request.HasScope(OpenIdConnectConstants.Scopes.OpenId) &&
                     (request.IsImplicitFlow() || request.IsHybridFlow()))
            {
                Options.Logger.LogError("The authorization request was rejected because " +
                                        "the mandatory 'nonce' parameter was missing.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "nonce parameter missing"
                }));
            }

            // Reject requests containing the id_token response_type if no openid scope has been received.
            else if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) &&
                     !request.HasScope(OpenIdConnectConstants.Scopes.OpenId))
            {
                Options.Logger.LogError("The authorization request was rejected because the 'openid' scope was missing.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "openid scope missing"
                }));
            }

            // Reject requests containing the code response_type if the token endpoint has been disabled.
            else if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code) && !Options.TokenEndpointPath.HasValue)
            {
                Options.Logger.LogError("The authorization request was rejected because the authorization code flow was disabled.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.UnsupportedResponseType,
                    ErrorDescription = "response_type=code is not supported by this server"
                }));
            }

            if (!string.IsNullOrEmpty(request.CodeChallenge) || !string.IsNullOrEmpty(request.CodeChallengeMethod))
            {
                // When code_challenge or code_challenge_method is specified, ensure the response_type includes "code".
                if (!request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code))
                {
                    Options.Logger.LogError("The authorization request was rejected because the response type " +
                                            "was not compatible with 'code_challenge'/'code_challenge_method'.");

                    return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "The 'code_challenge' and 'code_challenge_method' parameters " +
                                           "can only be used with a response type containing 'code'."
                    }));
                }

                if (!string.IsNullOrEmpty(request.CodeChallengeMethod))
                {
                    // Ensure a code_challenge was specified if a code_challenge_method was used.
                    if (string.IsNullOrEmpty(request.CodeChallenge))
                    {
                        Options.Logger.LogError("The authorization request was rejected " +
                                                "because the code_challenge was missing.");

                        return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidRequest,
                            ErrorDescription = "The 'code_challenge_method' parameter " +
                                               "cannot be used without 'code_challenge'."
                        }));
                    }

                    // If a code_challenge_method was specified, ensure the algorithm is supported.
                    if (request.CodeChallengeMethod != OpenIdConnectConstants.CodeChallengeMethods.Plain &&
                        request.CodeChallengeMethod != OpenIdConnectConstants.CodeChallengeMethods.Sha256)
                    {
                        Options.Logger.LogError("The authorization request was rejected because " +
                                                "the specified code challenge was not supported.");

                        return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidRequest,
                            ErrorDescription = "The specified code_challenge_method is not supported."
                        }));
                    }
                }
            }

            var context = new ValidateAuthorizationRequestContext(Context, Options, request);
            await Options.Provider.ValidateAuthorizationRequest(context);

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

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

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

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

            var notification = new HandleAuthorizationRequestContext(Context, Options, request);
            await Options.Provider.HandleAuthorizationRequest(notification);

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

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

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

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

            // If an authentication ticket was provided, stop processing
            // the request and return an authorization response.
            var ticket = notification.Ticket;

            if (ticket == null)
            {
                return(false);
            }

            return(await HandleSignInAsync(ticket));
        }
        private async Task <bool> InvokeAuthorizationEndpointAsync()
        {
            OpenIdConnectRequest request;

            if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                request = new OpenIdConnectRequest(Request.Query);
            }

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

                    return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "The mandatory 'Content-Type' header must be specified."
                    }));
                }

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

                    return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "The specified 'Content-Type' header is not valid."
                    }));
                }

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

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

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

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

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

            var @event = new ExtractAuthorizationRequestContext(Context, Options, request);
            await Options.Provider.ExtractAuthorizationRequest(@event);

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

                return(true);
            }

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

                return(false);
            }

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

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

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

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

            // client_id is mandatory parameter and MUST cause an error when missing.
            // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
            if (string.IsNullOrEmpty(request.ClientId))
            {
                Logger.LogError("The authorization request was rejected because " +
                                "the mandatory 'client_id' parameter was missing.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'client_id' parameter is missing."
                }));
            }

            // While redirect_uri was not mandatory in OAuth2, this parameter
            // is now declared as REQUIRED and MUST cause an error when missing.
            // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
            // To keep Security.OpenIdConnect.Server compatible with pure OAuth2 clients,
            // an error is only returned if the request was made by an OpenID Connect client.
            if (string.IsNullOrEmpty(request.RedirectUri) && request.HasScope(OpenIdConnectConstants.Scopes.OpenId))
            {
                Logger.LogError("The authorization request was rejected because " +
                                "the mandatory 'redirect_uri' parameter was missing.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'redirect_uri' parameter is missing."
                }));
            }

            if (!string.IsNullOrEmpty(request.RedirectUri))
            {
                // Note: when specified, redirect_uri MUST be an absolute URI.
                // See http://tools.ietf.org/html/rfc6749#section-3.1.2
                // and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
                //
                // Note: on Linux/macOS, "/path" URLs are treated as valid absolute file URLs.
                // To ensure relative redirect_uris are correctly rejected on these platforms,
                // an additional check using IsWellFormedOriginalString() is made here.
                // See https://github.com/dotnet/corefx/issues/22098 for more information.
                if (!Uri.TryCreate(request.RedirectUri, UriKind.Absolute, out Uri uri) || !uri.IsWellFormedOriginalString())
                {
                    Logger.LogError("The authorization request was rejected because the 'redirect_uri' parameter " +
                                    "didn't correspond to a valid absolute URL: {RedirectUri}.", request.RedirectUri);

                    return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "The 'redirect_uri' parameter must be a valid absolute URL."
                    }));
                }

                // Note: when specified, redirect_uri MUST NOT include a fragment component.
                // See http://tools.ietf.org/html/rfc6749#section-3.1.2
                // and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
                if (!string.IsNullOrEmpty(uri.Fragment))
                {
                    Logger.LogError("The authorization request was rejected because the 'redirect_uri' " +
                                    "contained a URL fragment: {RedirectUri}.", request.RedirectUri);

                    return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "The 'redirect_uri' parameter must not include a fragment."
                    }));
                }
            }

            // Reject requests missing the mandatory response_type parameter.
            if (string.IsNullOrEmpty(request.ResponseType))
            {
                Logger.LogError("The authorization request was rejected because " +
                                "the mandatory 'response_type' parameter was missing.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'response_type' parameter is missing."
                }));
            }

            // response_mode=query (explicit or not) and a response_type containing id_token
            // or token are not considered as a safe combination and MUST be rejected.
            // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security
            if (request.IsQueryResponseMode() && (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) ||
                                                  request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token)))
            {
                Logger.LogError("The authorization request was rejected because the 'response_type'/'response_mode' combination " +
                                "was invalid: {ResponseType} ; {ResponseMode}.", request.ResponseType, request.ResponseMode);

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The specified 'response_type'/'response_mode' combination is invalid."
                }));
            }

            // Reject OpenID Connect implicit/hybrid requests missing the mandatory nonce parameter.
            // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest,
            // http://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters
            // and http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken.
            if (string.IsNullOrEmpty(request.Nonce) && request.HasScope(OpenIdConnectConstants.Scopes.OpenId) &&
                (request.IsImplicitFlow() || request.IsHybridFlow()))
            {
                Logger.LogError("The authorization request was rejected because the mandatory 'nonce' parameter was missing.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'nonce' parameter is missing."
                }));
            }

            // Reject requests containing the id_token response_type if no openid scope has been received.
            if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) &&
                !request.HasScope(OpenIdConnectConstants.Scopes.OpenId))
            {
                Logger.LogError("The authorization request was rejected because the 'openid' scope was missing.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'openid' scope is missing."
                }));
            }

            // Reject requests containing the id_token response_type if no asymmetric signing key has been registered.
            if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) &&
                !Options.SigningCredentials.Any(credentials => credentials.Key is AsymmetricSecurityKey))
            {
                Logger.LogError("The authorization request was rejected because the 'id_token' response type could not be honored. " +
                                "To fix this error, consider registering a X.509 signing certificate or an ephemeral signing key.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.UnsupportedResponseType,
                    ErrorDescription = "The specified 'response_type' is not supported by this server."
                }));
            }

            // Reject requests containing the code response_type if the token endpoint has been disabled.
            if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code) && !Options.TokenEndpointPath.HasValue)
            {
                Logger.LogError("The authorization request was rejected because the authorization code flow was disabled.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.UnsupportedResponseType,
                    ErrorDescription = "The specified 'response_type' is not supported by this server."
                }));
            }

            // Reject requests specifying prompt=none with consent/login or select_account.
            if (request.HasPrompt(OpenIdConnectConstants.Prompts.None) && (request.HasPrompt(OpenIdConnectConstants.Prompts.Consent) ||
                                                                           request.HasPrompt(OpenIdConnectConstants.Prompts.Login) ||
                                                                           request.HasPrompt(OpenIdConnectConstants.Prompts.SelectAccount)))
            {
                Logger.LogError("The authorization request was rejected because an invalid prompt parameter was specified.");

                return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The specified 'prompt' parameter is invalid."
                }));
            }

            if (!string.IsNullOrEmpty(request.CodeChallenge) || !string.IsNullOrEmpty(request.CodeChallengeMethod))
            {
                // When code_challenge or code_challenge_method is specified, ensure the response_type includes "code".
                if (!request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code))
                {
                    Logger.LogError("The authorization request was rejected because the response type " +
                                    "was not compatible with 'code_challenge'/'code_challenge_method'.");

                    return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "The 'code_challenge' and 'code_challenge_method' parameters " +
                                           "can only be used with a response type containing 'code'."
                    }));
                }

                if (!string.IsNullOrEmpty(request.CodeChallengeMethod))
                {
                    // Ensure a code_challenge was specified if a code_challenge_method was used.
                    if (string.IsNullOrEmpty(request.CodeChallenge))
                    {
                        Logger.LogError("The authorization request was rejected because the code_challenge was missing.");

                        return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                        {
                            Error = OpenIdConnectConstants.Errors.InvalidRequest,
                            ErrorDescription = "The 'code_challenge_method' parameter " +
                                               "cannot be used without 'code_challenge'."
                        }));
                    }

                    // If a code_challenge_method was specified, ensure the algorithm is supported.
                    if (request.CodeChallengeMethod != OpenIdConnectConstants.CodeChallengeMethods.Plain &&
                        request.CodeChallengeMethod != OpenIdConnectConstants.CodeChallengeMethods.Sha256)
                    {
                        Logger.LogError("The authorization request was rejected because " +
                                        "the specified code challenge was not supported.");

                        return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse
                        {
                            Error = OpenIdConnectConstants.Errors.InvalidRequest,
                            ErrorDescription = "The specified code_challenge_method is not supported."
                        }));
                    }
                }
            }

            var context = new ValidateAuthorizationRequestContext(Context, Options, request);
            await Options.Provider.ValidateAuthorizationRequest(context);

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

                return(true);
            }

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

                return(false);
            }

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

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

            // Store the validated client_id/redirect_uri as request properties.
            request.SetProperty(OpenIdConnectConstants.Properties.ValidatedClientId, context.ClientId)
            .SetProperty(OpenIdConnectConstants.Properties.ValidatedRedirectUri, context.RedirectUri);

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

            var notification = new HandleAuthorizationRequestContext(Context, Options, request);
            await Options.Provider.HandleAuthorizationRequest(notification);

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

                return(true);
            }

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

                return(false);
            }

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

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

            // If an authentication ticket was provided, stop processing
            // the request and return an authorization response.
            var ticket = notification.Ticket;

            if (ticket == null)
            {
                return(false);
            }

            return(await HandleSignInAsync(ticket));
        }
Example #4
0
        private async Task <bool> InvokeAuthorizationEndpointAsync()
        {
            OpenIdConnectMessage request;

            if (string.Equals(Request.Method, "GET", StringComparison.OrdinalIgnoreCase))
            {
                // Create a new authorization request using the
                // parameters retrieved from the query string.
                request = new OpenIdConnectMessage(Request.Query)
                {
                    RequestType = OpenIdConnectRequestType.AuthenticationRequest
                };
            }

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

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

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

                // Create a new authorization request using the
                // parameters retrieved from the request form.
                request = new OpenIdConnectMessage(await Request.ReadFormAsync())
                {
                    RequestType = OpenIdConnectRequestType.AuthenticationRequest
                };
            }

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

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

            // Re-assemble the authorization request using the distributed cache if
            // a 'unique_id' parameter has been extracted from the received message.
            var identifier = request.GetRequestId();

            if (!string.IsNullOrEmpty(identifier))
            {
                var buffer = await Options.Cache.GetAsync($"asos-request:{identifier}");

                if (buffer == null)
                {
                    Options.Logger.LogError("A request_id was extracted from the authorization request ({RequestId}) " +
                                            "but no corresponding entry was found in the cache.", identifier);

                    return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "Invalid request: timeout expired."
                    }));
                }

                using (var stream = new MemoryStream(buffer))
                    using (var reader = new BinaryReader(stream)) {
                        // Make sure the stored authorization request
                        // has been serialized using the same method.
                        var version = reader.ReadInt32();
                        if (version != 1)
                        {
                            await Options.Cache.RemoveAsync($"asos-request:{identifier}");

                            Options.Logger.LogError("The authorization request retrieved from the cache was invalid.");

                            return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                                Error = OpenIdConnectConstants.Errors.InvalidRequest,
                                ErrorDescription = "Invalid request: timeout expired."
                            }));
                        }

                        for (int index = 0, length = reader.ReadInt32(); index < length; index++)
                        {
                            var name  = reader.ReadString();
                            var value = reader.ReadString();

                            // Skip restoring the parameter retrieved from the stored request
                            // if the OpenID Connect message extracted from the query string
                            // or the request form defined the same parameter.
                            if (!request.Parameters.ContainsKey(name))
                            {
                                request.SetParameter(name, value);
                            }
                        }
                    }
            }

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

            // client_id is mandatory parameter and MUST cause an error when missing.
            // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
            if (string.IsNullOrEmpty(request.ClientId))
            {
                Options.Logger.LogError("The authorization request was rejected because " +
                                        "the mandatory 'client_id' parameter was missing.");

                return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "client_id was missing"
                }));
            }

            // While redirect_uri was not mandatory in OAuth2, this parameter
            // is now declared as REQUIRED and MUST cause an error when missing.
            // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
            // To keep AspNet.Security.OpenIdConnect.Server compatible with pure OAuth2 clients,
            // an error is only returned if the request was made by an OpenID Connect client.
            if (string.IsNullOrEmpty(request.RedirectUri) && request.HasScope(OpenIdConnectConstants.Scopes.OpenId))
            {
                Options.Logger.LogError("The authorization request was rejected because " +
                                        "the mandatory 'redirect_uri' parameter was missing.");

                return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "redirect_uri must be included when making an OpenID Connect request"
                }));
            }

            if (!string.IsNullOrEmpty(request.RedirectUri))
            {
                // Note: when specified, redirect_uri MUST be an absolute URI.
                // See http://tools.ietf.org/html/rfc6749#section-3.1.2
                // and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
                Uri uri;
                if (!Uri.TryCreate(request.RedirectUri, UriKind.Absolute, out uri))
                {
                    Options.Logger.LogError("The authorization request was rejected because the 'redirect_uri' parameter " +
                                            "didn't correspond to a valid absolute URL: {RedirectUri}.", request.RedirectUri);

                    return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "redirect_uri must be absolute"
                    }));
                }

                // Note: when specified, redirect_uri MUST NOT include a fragment component.
                // See http://tools.ietf.org/html/rfc6749#section-3.1.2
                // and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
                else if (!string.IsNullOrEmpty(uri.Fragment))
                {
                    Options.Logger.LogError("The authorization request was rejected because the 'redirect_uri' " +
                                            "contained a URL segment: {RedirectUri}.", request.RedirectUri);

                    return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "redirect_uri must not include a fragment"
                    }));
                }
            }

            // Reject requests using the unsupported request parameter.
            if (!string.IsNullOrEmpty(request.GetParameter(OpenIdConnectConstants.Parameters.Request)))
            {
                Options.Logger.LogError("The authorization request was rejected because it contained " +
                                        "an unsupported parameter: {Parameter}.", "request");

                return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.RequestNotSupported,
                    ErrorDescription = "The request parameter is not supported."
                }));
            }

            // Reject requests using the unsupported request_uri parameter.
            else if (!string.IsNullOrEmpty(request.RequestUri))
            {
                Options.Logger.LogError("The authorization request was rejected because it contained " +
                                        "an unsupported parameter: {Parameter}.", "request_uri");

                return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.RequestUriNotSupported,
                    ErrorDescription = "The request_uri parameter is not supported."
                }));
            }

            // Reject requests missing the mandatory response_type parameter.
            else if (string.IsNullOrEmpty(request.ResponseType))
            {
                Options.Logger.LogError("The authorization request was rejected because " +
                                        "the mandatory 'response_type' parameter was missing.");

                return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "response_type parameter missing"
                }));
            }

            // Reject requests whose response_type parameter is unsupported.
            else if (!request.IsNoneFlow() && !request.IsAuthorizationCodeFlow() &&
                     !request.IsImplicitFlow() && !request.IsHybridFlow())
            {
                Options.Logger.LogError("The authorization request was rejected because the 'response_type' " +
                                        "parameter was invalid: {ResponseType}.", request.ResponseType);

                return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.UnsupportedResponseType,
                    ErrorDescription = "response_type unsupported"
                }));
            }

            // Reject requests whose response_mode is unsupported.
            else if (!request.IsFormPostResponseMode() && !request.IsFragmentResponseMode() && !request.IsQueryResponseMode())
            {
                Options.Logger.LogError("The authorization request was rejected because the 'response_mode' " +
                                        "parameter was invalid: {ResponseMode}.", request.ResponseMode);

                return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "response_mode unsupported"
                }));
            }

            // response_mode=query (explicit or not) and a response_type containing id_token
            // or token are not considered as a safe combination and MUST be rejected.
            // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security
            else if (request.IsQueryResponseMode() && (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) ||
                                                       request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token)))
            {
                Options.Logger.LogError("The authorization request was rejected because the 'response_type'/'response_mode' combination " +
                                        "was unsafe: {ResponseType} ; {ResponseMode}.", request.ResponseType, request.ResponseMode);

                return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "response_type/response_mode combination unsupported"
                }));
            }

            // Reject OpenID Connect implicit/hybrid requests missing the mandatory nonce parameter.
            // See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest,
            // http://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters
            // and http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken.
            else if (string.IsNullOrEmpty(request.Nonce) && request.HasScope(OpenIdConnectConstants.Scopes.OpenId) &&
                     (request.IsImplicitFlow() || request.IsHybridFlow()))
            {
                Options.Logger.LogError("The authorization request was rejected because " +
                                        "the mandatory 'nonce' parameter was missing.");

                return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "nonce parameter missing"
                }));
            }

            // Reject requests containing the id_token response_mode if no openid scope has been received.
            else if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) &&
                     !request.HasScope(OpenIdConnectConstants.Scopes.OpenId))
            {
                Options.Logger.LogError("The authorization request was rejected because the 'openid' scope was missing.");

                return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "openid scope missing"
                }));
            }

            // Reject requests containing the code response_mode if the token endpoint has been disabled.
            else if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code) && !Options.TokenEndpointPath.HasValue)
            {
                Options.Logger.LogError("The authorization request was rejected because the authorization code flow was disabled.");

                return(await SendAuthorizationResponseAsync(request, new OpenIdConnectMessage {
                    Error = OpenIdConnectConstants.Errors.UnsupportedResponseType,
                    ErrorDescription = "response_type=code is not supported by this server"
                }));
            }

            var context = new ValidateAuthorizationRequestContext(Context, Options, request);
            await Options.Provider.ValidateAuthorizationRequest(context);

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

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

            identifier = request.GetRequestId();
            if (string.IsNullOrEmpty(identifier))
            {
                // Generate a new 256-bits identifier and associate it with the authorization request.
                identifier = Options.RandomNumberGenerator.GenerateKey(length: 256 / 8);
                request.SetRequestId(identifier);

                using (var stream = new MemoryStream())
                    using (var writer = new BinaryWriter(stream)) {
                        writer.Write(/* version: */ 1);
                        writer.Write(request.Parameters.Count);

                        foreach (var parameter in request.Parameters)
                        {
                            writer.Write(parameter.Key);
                            writer.Write(parameter.Value);
                        }

                        // Serialize the authorization request.
                        var bytes = stream.ToArray();

                        // Store the authorization request in the distributed cache.
                        await Options.Cache.SetAsync($"asos-request:{identifier}", bytes, new DistributedCacheEntryOptions {
                            AbsoluteExpiration = Options.SystemClock.UtcNow + TimeSpan.FromHours(1)
                        });
                    }
            }

            var notification = new HandleAuthorizationRequestContext(Context, Options, request);
            await Options.Provider.HandleAuthorizationRequest(notification);

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

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

            return(false);
        }