/// <summary>
 /// Called at the final stage of an incoming authorization endpoint request before the execution continues on to the web application component
 /// responsible for producing the html response. Anything present in the OWIN pipeline following the Authorization Server may produce the
 /// response for the authorization page. If running on IIS any ASP.NET technology running on the server may produce the response for the
 /// authorization page. If the web application wishes to produce the response directly in the AuthorizationEndpoint call it may write to the
 /// context.Response directly and should call context.RequestCompleted to stop other handlers from executing. If the web application wishes
 /// to grant the authorization directly in the AuthorizationEndpoint call it cay call context.OwinContext.Authentication.SignIn with the
 /// appropriate ClaimsIdentity and should call context.RequestCompleted to stop other handlers from executing.
 /// </summary>
 /// <param name="context">The context of the event carries information in and results out.</param>
 /// <returns>Task to enable asynchronous execution</returns>
 public virtual Task AuthorizationEndpoint(AuthorizationEndpointContext context) => OnAuthorizationEndpoint(context);
Example #2
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.ToDictionary())
                {
                    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))
                {
                    Logger.LogInformation("A malformed request has been received by the authorization endpoint.");

                    return(await SendErrorPageAsync(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))
                {
                    Logger.LogInformation("A malformed request has been received by the authorization endpoint.");

                    return(await SendErrorPageAsync(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.
                var form = await Request.ReadFormAsync(Context.RequestAborted);

                request = new OpenIdConnectMessage(form.ToDictionary())
                {
                    RequestType = OpenIdConnectRequestType.AuthenticationRequest
                };
            }

            else
            {
                Logger.LogInformation("A malformed request has been received by the authorization endpoint.");

                return(await SendErrorPageAsync(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.GetUniqueIdentifier();

            if (!string.IsNullOrEmpty(identifier))
            {
                var buffer = await Options.Cache.GetAsync(identifier);

                if (buffer == null)
                {
                    Logger.LogInformation("A unique_id has been provided but no corresponding " +
                                          "OpenID Connect request has been found in the distributed cache.");

                    return(await SendErrorPageAsync(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(identifier);

                            Logger.LogError("An invalid OpenID Connect request has been found in the distributed cache.");

                            return(await SendErrorPageAsync(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 ASP.NET 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))
            {
                return(await SendErrorPageAsync(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))
            {
                return(await SendErrorPageAsync(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))
                {
                    return(await SendErrorPageAsync(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))
                {
                    return(await SendErrorPageAsync(new OpenIdConnectMessage {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "redirect_uri must not include a fragment"
                    }));
                }

                // Note: when specified, redirect_uri SHOULD require the use of TLS
                // http://tools.ietf.org/html/rfc6749#section-3.1.2.1
                // and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
                else if (!Options.AllowInsecureHttp && string.Equals(uri.Scheme, "http", StringComparison.OrdinalIgnoreCase))
                {
                    return(await SendErrorPageAsync(new OpenIdConnectMessage {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "redirect_uri does not meet the security requirements"
                    }));
                }
            }

            var clientNotification = new ValidateClientRedirectUriContext(Context, Options, request);
            await Options.Provider.ValidateClientRedirectUri(clientNotification);

            // Reject the authorization request if the redirect_uri was not validated.
            if (!clientNotification.IsValidated)
            {
                Logger.LogDebug("Unable to validate client information");

                return(await SendErrorPageAsync(new OpenIdConnectMessage {
                    Error = clientNotification.Error ?? OpenIdConnectConstants.Errors.InvalidClient,
                    ErrorDescription = clientNotification.ErrorDescription,
                    ErrorUri = clientNotification.ErrorUri
                }));
            }

            // Reject requests using the unsupported request parameter.
            if (!string.IsNullOrEmpty(request.GetParameter(OpenIdConnectConstants.Parameters.Request)))
            {
                Logger.LogDebug("The authorization request contained the unsupported request parameter.");

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

            // Reject requests using the unsupported request_uri parameter.
            else if (!string.IsNullOrEmpty(request.RequestUri))
            {
                Logger.LogDebug("The authorization request contained the unsupported request_uri parameter.");

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

            // Reject requests missing the mandatory response_type parameter.
            else if (string.IsNullOrEmpty(request.ResponseType))
            {
                Logger.LogDebug("Authorization request missing required response_type parameter");

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

            // Reject requests whose response_type parameter is unsupported.
            else if (!request.IsNoneFlow() && !request.IsAuthorizationCodeFlow() &&
                     !request.IsImplicitFlow() && !request.IsHybridFlow())
            {
                Logger.LogDebug("Authorization request contains unsupported response_type parameter");

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

            // Reject requests whose response_mode is unsupported.
            else if (!request.IsFormPostResponseMode() && !request.IsFragmentResponseMode() && !request.IsQueryResponseMode())
            {
                Logger.LogDebug("Authorization request contains unsupported response_mode parameter");

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

            // 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)))
            {
                Logger.LogDebug("Authorization request contains unsafe response_type/response_mode combination");

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

            // 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()))
            {
                Logger.LogDebug("The 'nonce' parameter was missing");

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

            // 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))
            {
                Logger.LogDebug("The 'openid' scope part was missing");

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

            // Reject requests containing the code response_mode if the token endpoint has been disabled.
            else if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code) &&
                     !Options.TokenEndpointPath.HasValue)
            {
                Logger.LogDebug("Authorization request contains the disabled code response_type");

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

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

            // Stop processing the request if Validated was not called.
            if (!validationNotification.IsValidated)
            {
                return(await SendErrorRedirectAsync(request, new OpenIdConnectMessage {
                    Error = validationNotification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = validationNotification.ErrorDescription,
                    ErrorUri = validationNotification.ErrorUri,
                    RedirectUri = request.RedirectUri,
                    State = request.State
                }));
            }

            identifier = request.GetUniqueIdentifier();
            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.SetUniqueIdentifier(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);
                        }

                        // Store the authorization request in the distributed cache.
                        await Options.Cache.SetAsync(request.GetUniqueIdentifier(), options => {
                            options.SetAbsoluteExpiration(TimeSpan.FromHours(1));

                            return(stream.ToArray());
                        });
                    }
            }

            var notification = new AuthorizationEndpointContext(Context, Options, request);
            await Options.Provider.AuthorizationEndpoint(notification);

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

            return(false);
        }
 /// <summary>
 /// Called at the final stage of an incoming authorization endpoint request before the execution continues on to the web application component 
 /// responsible for producing the html response. Anything present in the OWIN pipeline following the Authorization Server may produce the
 /// response for the authorization page. If running on IIS any ASP.NET technology running on the server may produce the response for the 
 /// authorization page. If the web application wishes to produce the response directly in the AuthorizationEndpoint call it may write to the 
 /// context.Response directly and should call context.RequestCompleted to stop other handlers from executing. If the web application wishes
 /// to grant the authorization directly in the AuthorizationEndpoint call it cay call context.OwinContext.Authentication.SignIn with the
 /// appropriate ClaimsIdentity and should call context.RequestCompleted to stop other handlers from executing.
 /// </summary>
 /// <param name="context">The context of the event carries information in and results out.</param>
 /// <returns>Task to enable asynchronous execution</returns>
 public virtual Task AuthorizationEndpoint(AuthorizationEndpointContext context) => OnAuthorizationEndpoint(context);