diff --git a/src/OpenIddict.Core/OpenIddictProvider.Authentication.cs b/src/OpenIddict.Core/OpenIddictProvider.Authentication.cs index cb173f97..79336d4d 100644 --- a/src/OpenIddict.Core/OpenIddictProvider.Authentication.cs +++ b/src/OpenIddict.Core/OpenIddictProvider.Authentication.cs @@ -63,6 +63,34 @@ namespace OpenIddict { return; } + // If the user is connected, ensure that a corresponding profile exists and that + // the appropriate set of scopes is requested to prevent personal data leakage. + if (context.HttpContext.User.Identities.Any(identity => identity.IsAuthenticated)) { + // Ensure the user profile still exists in the database. + var user = await manager.FindByIdAsync(context.HttpContext.User.GetUserId()); + if (user == null) { + context.Reject( + error: OpenIdConnectConstants.Errors.ServerError, + description: "An internal error has occurred."); + + return; + } + + // Return an error if the username corresponds to the registered + // email address and if the "email" scope has not been requested. + if (context.Request.HasScope(OpenIdConnectConstants.Scopes.Profile) && + !context.Request.HasScope(OpenIdConnectConstants.Scopes.Email) && + string.Equals(await manager.GetUserNameAsync(user), + await manager.GetEmailAsync(user), + StringComparison.OrdinalIgnoreCase)) { + context.Reject( + error: OpenIdConnectConstants.Errors.InvalidRequest, + description: "The 'email' scope is required."); + + return; + } + } + // Run additional checks for prompt=none requests. if (string.Equals(context.Request.Prompt, "none", StringComparison.Ordinal)) { // If the user is not authenticated, return an error to the client application. @@ -96,30 +124,6 @@ namespace OpenIddict { return; } - - // Ensure the user profile still exists in the database. - var user = await manager.FindByIdAsync(principal.GetUserId()); - if (user == null) { - context.Reject( - error: OpenIdConnectConstants.Errors.InvalidRequest, - description: "The id_token_hint parameter is invalid."); - - return; - } - - // Return an error if the username corresponds to the registered - // email address and if the "email" scope has not been requested. - if (context.Request.HasScope(OpenIdConnectConstants.Scopes.Profile) && - !context.Request.HasScope(OpenIdConnectConstants.Scopes.Email) && - string.Equals(await manager.GetUserNameAsync(user), - await manager.GetEmailAsync(user), - StringComparison.OrdinalIgnoreCase)) { - context.Reject( - error: OpenIdConnectConstants.Errors.InvalidRequest, - description: "The 'email' scope is required."); - - return; - } } context.Validate(); diff --git a/src/OpenIddict.Mvc/OpenIddictController.cs b/src/OpenIddict.Mvc/OpenIddictController.cs index 21689604..c337c394 100644 --- a/src/OpenIddict.Mvc/OpenIddictController.cs +++ b/src/OpenIddict.Mvc/OpenIddictController.cs @@ -96,6 +96,18 @@ namespace OpenIddict.Mvc { [Authorize, HttpPost, ValidateAntiForgeryToken] public virtual async Task Accept() { + // Note: when a fatal error occurs during the request processing, an OpenID Connect response + // is prematurely forged and added to the ASP.NET context by OpenIdConnectServerHandler. + // In this case, the OpenID Connect request is null and cannot be used. + // When the user agent can be safely redirected to the client application, + // OpenIdConnectServerHandler automatically handles the error and MVC is not invoked. + // You can safely remove this part and let AspNet.Security.OpenIdConnect.Server automatically + // handle the unrecoverable errors by switching ApplicationCanDisplayErrors to false. + var response = HttpContext.GetOpenIdConnectResponse(); + if (response != null) { + return View("Error", response); + } + // Extract the authorization request from the cache, // the query string or the request form. var request = HttpContext.GetOpenIdConnectRequest(); @@ -115,19 +127,6 @@ namespace OpenIddict.Mvc { }); } - // Return an error if the username corresponds to the registered - // email address and if the "email" scope has not been requested. - if (request.HasScope(OpenIdConnectConstants.Scopes.Profile) && - !request.HasScope(OpenIdConnectConstants.Scopes.Email) && - string.Equals(await Manager.GetUserNameAsync(user), - await Manager.GetEmailAsync(user), - StringComparison.OrdinalIgnoreCase)) { - return View("Error", new OpenIdConnectMessage { - Error = OpenIdConnectConstants.Errors.InvalidRequest, - ErrorDescription = "The 'email' scope is required." - }); - } - // Create a new ClaimsIdentity containing the claims that // will be used to create an id_token, a token or a code. var identity = await Manager.CreateIdentityAsync(user, request.GetScopes()); @@ -172,6 +171,18 @@ namespace OpenIddict.Mvc { [Authorize, HttpPost, ValidateAntiForgeryToken] public virtual IActionResult Deny() { + // Note: when a fatal error occurs during the request processing, an OpenID Connect response + // is prematurely forged and added to the ASP.NET context by OpenIdConnectServerHandler. + // In this case, the OpenID Connect request is null and cannot be used. + // When the user agent can be safely redirected to the client application, + // OpenIdConnectServerHandler automatically handles the error and MVC is not invoked. + // You can safely remove this part and let AspNet.Security.OpenIdConnect.Server automatically + // handle the unrecoverable errors by switching ApplicationCanDisplayErrors to false. + var response = HttpContext.GetOpenIdConnectResponse(); + if (response != null) { + return View("Error", response); + } + // Extract the authorization request from the cache, // the query string or the request form. var request = HttpContext.GetOpenIdConnectRequest();