public override async Task ValidateAuthorizationRequest(ValidateAuthorizationRequestContext context) { // Note: the OpenID Connect server middleware supports the authorization code, implicit and hybrid flows // but this authorization provider only accepts response_type=code authorization/authentication requests. // You may consider relaxing it to support the implicit or hybrid flows. In this case, consider adding // checks rejecting implicit/hybrid authorization requests when the client is a confidential application. if (!context.Request.IsAuthorizationCodeFlow()) { context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedResponseType, description: "Only the authorization code flow is supported by this authorization server"); return; } // Note: to support custom response modes, the OpenID Connect server middleware doesn't // reject unknown modes before the ApplyAuthorizationResponse event is invoked. // To ensure invalid modes are rejected early enough, a check is made here. if (!string.IsNullOrEmpty(context.Request.ResponseMode) && !context.Request.IsFormPostResponseMode() && !context.Request.IsFragmentResponseMode() && !context.Request.IsQueryResponseMode()) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The specified response_mode is unsupported."); return; } var database = context.HttpContext.RequestServices.GetRequiredService<ApplicationContext>(); // Retrieve the application details corresponding to the requested client_id. var application = await (from entity in database.Applications where entity.ApplicationID == context.ClientId select entity).SingleOrDefaultAsync(context.HttpContext.RequestAborted); if (application == null) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Application not found in the database: ensure that your client_id is correct"); return; } if (!string.IsNullOrEmpty(context.RedirectUri) && !string.Equals(context.RedirectUri, application.RedirectUri, StringComparison.Ordinal)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "Invalid redirect_uri"); return; } context.Validate(application.RedirectUri); }
public override Task ValidateAuthorizationRequest(ValidateAuthorizationRequestContext context) { // Note: the OpenID Connect server middleware supports the authorization code, implicit and hybrid flows // but this authorization provider only accepts response_type=code authorization/authentication requests. // You may consider relaxing it to support the implicit or hybrid flows. In this case, consider adding // checks rejecting implicit/hybrid authorization requests when the client is a confidential application. if (!context.Request.IsAuthorizationCodeFlow()) { context.Reject( error: OpenIdConnectConstants.Errors.UnsupportedResponseType, description: "Only the authorization code flow is supported by this authorization server."); return Task.FromResult(0); } // Note: to support custom response modes, the OpenID Connect server middleware doesn't // reject unknown modes before the ApplyAuthorizationResponse event is invoked. // To ensure invalid modes are rejected early enough, a check is made here. if (!string.IsNullOrEmpty(context.Request.ResponseMode) && !context.Request.IsFormPostResponseMode() && !context.Request.IsFragmentResponseMode() && !context.Request.IsQueryResponseMode()) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidRequest, description: "The specified response_mode is unsupported."); return Task.FromResult(0); } // Ensure the client_id parameter corresponds to the Postman client. if (!string.Equals(context.Request.ClientId, "postman", StringComparison.Ordinal)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "The specified client_id is unknown."); return Task.FromResult(0); } // Ensure the redirect_uri parameter corresponds to the Postman client. if (!string.Equals(context.Request.RedirectUri, "https://www.getpostman.com/oauth2/callback", StringComparison.Ordinal)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidClient, description: "The specified redirect_uri is invalid."); return Task.FromResult(0); } context.Validate(); return Task.FromResult(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); }
private async Task <bool> InvokeAuthorizationEndpointAsync() { OpenIdConnectRequest request; if (HttpMethods.IsGet(Request.Method)) { request = new OpenIdConnectRequest(Request.Query); } else if (HttpMethods.IsPost(Request.Method)) { // 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(Context.RequestAborted)); } 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 ASP.NET context. Context.SetOpenIdConnectRequest(request); var @event = new ExtractAuthorizationRequestContext(Context, Scheme, Options, request); await Provider.ExtractAuthorizationRequest(@event); if (@event.Result != null) { if (@event.Result.Handled) { Logger.LogDebug("The authorization request was handled in user code."); return(true); } else if (@event.Result.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 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)) { 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, Scheme, Options, request); await Provider.ValidateAuthorizationRequest(context); if (context.Result != null) { if (context.Result.Handled) { Logger.LogDebug("The authorization request was handled in user code."); return(true); } else if (context.Result.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, Scheme, Options, request); await Provider.HandleAuthorizationRequest(notification); if (notification.Result != null) { if (notification.Result.Handled) { Logger.LogDebug("The authorization request was handled in user code."); return(true); } else if (notification.Result.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 SignInAsync(ticket)); }
/// <summary> /// Called for each request to the authorization endpoint to determine if the request is valid and should continue. /// The default behavior when using the OpenIdConnectServerProvider is to assume well-formed requests, with /// validated client redirect URI, should continue processing. An application may add any additional constraints. /// </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 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)) { 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)) { 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(Context.RequestAborted)); } else { 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 ASP.NET 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) { 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)) { 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)) { 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)) { 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)) { 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)) { 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))) { 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 = "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())) { 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)) { 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) { 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)) { 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) { return(true); } else if (context.Skipped) { return(false); } else if (!context.IsValidated) { 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) { 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)); }