Browse Source

Introduce new log messages in OpenIddictProvider and throw exceptions when appropriate

pull/137/head
Massimiliano Donini 10 years ago
committed by Kévin Chalet
parent
commit
758e1266af
  1. 94
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs
  2. 81
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs
  3. 23
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs
  4. 7
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs
  5. 6
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs
  6. 32
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs
  7. 19
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Userinfo.cs

94
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs

@ -15,6 +15,7 @@ using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace OpenIddict.Infrastructure {
public partial class OpenIddictProvider<TUser, TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
@ -37,6 +38,9 @@ namespace OpenIddict.Infrastructure {
// Retrieve the application details corresponding to the requested client_id.
var application = await services.Applications.FindByIdAsync(context.ClientId);
if (application == null) {
services.Logger.LogError("The authorization request was rejected because the client " +
"application was not found: '{ClientId}'.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Application not found in the database: ensure that your client_id is correct.");
@ -45,6 +49,9 @@ namespace OpenIddict.Infrastructure {
}
if (!await services.Applications.ValidateRedirectUriAsync(application, context.RedirectUri)) {
services.Logger.LogError("The authorization request was rejected because the redirect_uri " +
"was invalid: '{RedirectUri}'.", context.RedirectUri);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid redirect_uri.");
@ -72,6 +79,10 @@ namespace OpenIddict.Infrastructure {
// Ensure the user profile still exists in the database.
var user = await services.Users.GetUserAsync(context.HttpContext.User);
if (user == null) {
services.Logger.LogError("The authorization request was rejected because the profile corresponding " +
"to the logged in user was not found in the database: {Identifier}.",
context.HttpContext.User.GetClaim(ClaimTypes.NameIdentifier));
context.Reject(
error: OpenIdConnectConstants.Errors.ServerError,
description: "An internal error has occurred.");
@ -88,6 +99,10 @@ namespace OpenIddict.Infrastructure {
var email = await services.Users.GetEmailAsync(user);
if (!string.IsNullOrEmpty(email) && string.Equals(username, email, StringComparison.OrdinalIgnoreCase)) {
services.Logger.LogError("The authorization request was rejected because the 'email' scope was not requested: " +
"to prevent data leakage, the 'email' scope must be granted when the username" +
"is identical to the email address associated with the user ({Username}).", username);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "The 'email' scope is required.");
@ -102,6 +117,8 @@ namespace OpenIddict.Infrastructure {
// If the user is not authenticated, return an error to the client application.
// See http://openid.net/specs/openid-connect-core-1_0.html#Authenticates
if (!context.HttpContext.User.Identities.Any(identity => identity.IsAuthenticated)) {
services.Logger.LogError("The prompt=none authorization request was rejected because the user was not logged in.");
context.Reject(
error: OpenIdConnectConstants.Errors.LoginRequired,
description: "The user must be authenticated.");
@ -112,6 +129,9 @@ namespace OpenIddict.Infrastructure {
// Ensure that the authentication cookie contains the required NameIdentifier claim.
var identifier = context.HttpContext.User.GetClaim(ClaimTypes.NameIdentifier);
if (string.IsNullOrEmpty(identifier)) {
services.Logger.LogError("The prompt=none authorization request was rejected because the user session " +
"was invalid and didn't contain the mandatory ClaimTypes.NameIdentifier claim.");
context.Reject(
error: OpenIdConnectConstants.Errors.ServerError,
description: "The authorization request cannot be processed.");
@ -134,6 +154,8 @@ namespace OpenIddict.Infrastructure {
// and that the identity token corresponds to the authenticated user.
if (!principal.HasClaim(OpenIdConnectConstants.Claims.Audience, context.Request.ClientId) ||
!principal.HasClaim(ClaimTypes.NameIdentifier, identifier)) {
services.Logger.LogError("The prompt=none authorization request was rejected because the id_token_hint was invalid.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "The id_token_hint parameter is invalid.");
@ -148,46 +170,50 @@ namespace OpenIddict.Infrastructure {
public override async Task HandleAuthorizationRequest([NotNull] HandleAuthorizationRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
// Only handle prompt=none requests at this stage.
if (!string.Equals(context.Request.Prompt, "none", StringComparison.Ordinal)) {
return;
}
if (string.Equals(context.Request.Prompt, "none", StringComparison.Ordinal)) {
// Note: principal is guaranteed to be non-null since ValidateAuthorizationRequest
// rejects prompt=none requests missing or having an invalid id_token_hint.
var principal = await context.HttpContext.Authentication.AuthenticateAsync(context.Options.AuthenticationScheme);
Debug.Assert(principal != null, "The principal extracted from the id_token_hint shouldn't be null.");
// Note: principal is guaranteed to be non-null since ValidateAuthorizationRequest
// rejects prompt=none requests missing or having an invalid id_token_hint.
var principal = await context.HttpContext.Authentication.AuthenticateAsync(context.Options.AuthenticationScheme);
Debug.Assert(principal != null);
// Note: user may be null if the user was removed after
// the initial check made by ValidateAuthorizationRequest.
// In this case, ignore the prompt=none request and
// continue to the next middleware in the pipeline.
var user = await services.Users.GetUserAsync(principal);
if (user == null) {
return;
}
// Note: user may be null if the user was removed after
// the initial check made by ValidateAuthorizationRequest.
// In this case, throw an exception to abort the request.
var user = await services.Users.GetUserAsync(principal);
if (user == null) {
throw new InvalidOperationException("The prompt=none authorization request was aborted because the profile " +
"corresponding to the logged in user was not found in the database.");
}
// Note: filtering the username is not needed at this stage as OpenIddictController.Accept
// and OpenIddictProvider.GrantResourceOwnerCredentials are expected to reject requests that
// don't include the "email" scope if the username corresponds to the registed email address.
var identity = await services.Users.CreateIdentityAsync(user, context.Request.GetScopes());
Debug.Assert(identity != null);
// Note: filtering the username is not needed at this stage as OpenIddictController.Accept
// and OpenIddictProvider.GrantResourceOwnerCredentials are expected to reject requests that
// don't include the "email" scope if the username corresponds to the registed email address.
var identity = await services.Users.CreateIdentityAsync(user, context.Request.GetScopes());
if (identity == null) {
throw new InvalidOperationException("The authorization request failed because the user manager returned a null " +
$"identity for user '{await services.Users.GetUserNameAsync(user)}'.");
}
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
ticket.SetResources(context.Request.GetResources());
ticket.SetScopes(context.Request.GetScopes());
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
// Call SignInAsync to create and return a new OpenID Connect response containing the serialized code/tokens.
await context.HttpContext.Authentication.SignInAsync(ticket.AuthenticationScheme, ticket.Principal, ticket.Properties);
ticket.SetResources(context.Request.GetResources());
ticket.SetScopes(context.Request.GetScopes());
// Mark the response as handled
// to skip the rest of the pipeline.
context.HandleResponse();
// Call SignInAsync to create and return a new OpenID Connect response containing the serialized code/tokens.
await context.HttpContext.Authentication.SignInAsync(ticket.AuthenticationScheme, ticket.Principal, ticket.Properties);
return;
}
// Mark the response as handled
// to skip the rest of the pipeline.
context.HandleResponse();
context.SkipToNextMiddleware();
}
}
}

81
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs

@ -14,6 +14,7 @@ using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace OpenIddict.Infrastructure {
public partial class OpenIddictProvider<TUser, TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
@ -25,6 +26,9 @@ namespace OpenIddict.Infrastructure {
// resource owner password credentials and custom grants but OpenIddict uses a stricter policy rejecting custom grants.
if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsRefreshTokenGrantType() &&
!context.Request.IsPasswordGrantType() && !context.Request.IsClientCredentialsGrantType()) {
services.Logger.LogError("The token request was rejected because the '{Grant}' " +
"grant is not supported.", context.Request.GrantType);
context.Reject(
error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
description: "Only authorization code, refresh token, client credentials " +
@ -59,6 +63,9 @@ namespace OpenIddict.Infrastructure {
// cannot use an authorization code or a refresh token if it's not
// the intended audience, even if client authentication was skipped.
if (string.IsNullOrEmpty(context.ClientId)) {
services.Logger.LogInformation("The token request validation process was skipped " +
"because the client_id parameter was missing or empty.");
context.Skip();
return;
@ -67,6 +74,9 @@ namespace OpenIddict.Infrastructure {
// Retrieve the application details corresponding to the requested client_id.
var application = await services.Applications.FindByIdAsync(context.ClientId);
if (application == null) {
services.Logger.LogError("The token request was rejected because the client " +
"application was not found: '{ClientId}'.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Application not found in the database: ensure that your client_id is correct.");
@ -78,6 +88,9 @@ namespace OpenIddict.Infrastructure {
var type = await services.Applications.GetClientTypeAsync(application);
if (!string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase) &&
!string.IsNullOrEmpty(context.ClientSecret)) {
services.Logger.LogError("The token request was rejected because the public application '{ClientId}' " +
"was not allowed to send a client secret.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Public clients are not allowed to send a client_secret.");
@ -88,6 +101,9 @@ namespace OpenIddict.Infrastructure {
// Confidential applications MUST authenticate to protect them from impersonation attacks.
else if (!string.Equals(type, OpenIddictConstants.ClientTypes.Public)) {
if (string.IsNullOrEmpty(context.ClientSecret)) {
services.Logger.LogError("The token request was rejected because the confidential application " +
"'{ClientId}' didn't specify a client secret.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Missing credentials: ensure that you specified a client_secret.");
@ -96,6 +112,9 @@ namespace OpenIddict.Infrastructure {
}
if (!await services.Applications.ValidateSecretAsync(application, context.ClientSecret)) {
services.Logger.LogError("The token request was rejected because the confidential application " +
"'{ClientId}' didn't specify valid client credentials.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid credentials: ensure that you specified a correct client_secret.");
@ -111,8 +130,13 @@ namespace OpenIddict.Infrastructure {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
// Retrieve the application details corresponding to the requested client_id.
// Note: this call shouldn't return a null instance, but a race condition may occur
// if the application was removed after the initial check made by ValidateTokenRequest.
var application = await services.Applications.FindByIdAsync(context.ClientId);
Debug.Assert(application != null);
if (application == null) {
throw new InvalidOperationException("The token request was aborted because the client application corresponding " +
$"to the '{context.ClientId}' identifier was not found in the database.");
}
var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
@ -140,11 +164,12 @@ namespace OpenIddict.Infrastructure {
public override async Task GrantRefreshToken([NotNull] GrantRefreshTokenContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
var principal = context.Ticket?.Principal;
Debug.Assert(principal != null);
var user = await services.Users.GetUserAsync(principal);
var user = await services.Users.GetUserAsync(context.Ticket.Principal);
if (user == null) {
services.Logger.LogError("The token request was rejected because the user profile associated " +
"with the refresh token was not found in the database: '{Identifier}'.",
context.Ticket.Principal.GetClaim(ClaimTypes.NameIdentifier));
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The refresh token is no longer valid.");
@ -152,20 +177,16 @@ namespace OpenIddict.Infrastructure {
return;
}
// Try to extract the token identifier from the authentication ticket.
// If the identifier cannot be found, the revocation check is skipped.
// Extract the token identifier from the refresh token.
var identifier = context.Ticket.GetTicketId();
if (string.IsNullOrEmpty(identifier)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The refresh token is no longer valid.");
return;
}
Debug.Assert(!string.IsNullOrEmpty(identifier),
"The refresh token should contain a ticket identifier.");
// Retrieve the token from the database and ensure it is still valid.
var token = await services.Tokens.FindByIdAsync(identifier);
if (token == null) {
services.Logger.LogError("The token request was rejected because the refresh token was revoked.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The refresh token is no longer valid.");
@ -183,7 +204,10 @@ namespace OpenIddict.Infrastructure {
// Note: the "scopes" property stored in context.AuthenticationTicket is automatically
// updated by ASOS when the client application requests a restricted scopes collection.
var identity = await services.Users.CreateIdentityAsync(user, context.Ticket.GetScopes());
Debug.Assert(identity != null);
if (identity == null) {
throw new InvalidOperationException("The token request failed because the user manager returned a null " +
$"identity for user '{await services.Users.GetUserNameAsync(user)}'.");
}
// Create a new authentication ticket holding the user identity but
// reuse the authentication properties stored in the refresh token.
@ -200,6 +224,9 @@ namespace OpenIddict.Infrastructure {
var user = await services.Users.FindByNameAsync(context.UserName);
if (user == null) {
services.Logger.LogError("The token request was rejected because no user profile corresponding to " +
"the specified username was found: '{Username}'.", context.UserName);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid credentials.");
@ -209,6 +236,9 @@ namespace OpenIddict.Infrastructure {
// Ensure the user is allowed to sign in.
if (!await services.SignIn.CanSignInAsync(user)) {
services.Logger.LogError("The token request was rejected because the user '{Username}' " +
"was not allowed to sign in.", context.UserName);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The user is not allowed to sign in.");
@ -218,6 +248,9 @@ namespace OpenIddict.Infrastructure {
// Ensure the user is not already locked out.
if (services.Users.SupportsUserLockout && await services.Users.IsLockedOutAsync(user)) {
services.Logger.LogError("The token request was rejected because the account '{Username}' " +
"was locked out to prevent brute force attacks.", context.UserName);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Account locked out.");
@ -227,6 +260,9 @@ namespace OpenIddict.Infrastructure {
// Ensure the password is valid.
if (!await services.Users.CheckPasswordAsync(user, context.Password)) {
services.Logger.LogError("The token request was rejected because the password didn't match " +
"the password associated with the account '{Username}'.", context.UserName);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid credentials.");
@ -236,6 +272,9 @@ namespace OpenIddict.Infrastructure {
// Ensure the user is not locked out.
if (await services.Users.IsLockedOutAsync(user)) {
services.Logger.LogError("The token request was rejected because the account '{Username}' " +
"was locked out to prevent brute force attacks.", context.UserName);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Account locked out.");
@ -251,6 +290,9 @@ namespace OpenIddict.Infrastructure {
// Reject the token request if two-factor authentication has been enabled by the user.
if (services.Users.SupportsUserTwoFactor && await services.Users.GetTwoFactorEnabledAsync(user)) {
services.Logger.LogError("The token request was rejected because two-factor authentication " +
"was required for the account '{Username}.", context.UserName);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Two-factor authentication is required for this account.");
@ -267,6 +309,10 @@ namespace OpenIddict.Infrastructure {
var email = await services.Users.GetEmailAsync(user);
if (!string.IsNullOrEmpty(email) && string.Equals(username, email, StringComparison.OrdinalIgnoreCase)) {
services.Logger.LogError("The token request was rejected because the 'email' scope was not requested: " +
"to prevent data leakage, the 'email' scope must be granted when the username" +
"is identical to the email address associated with the user ({Username}).", username);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "The 'email' scope is required.");
@ -276,7 +322,10 @@ namespace OpenIddict.Infrastructure {
}
var identity = await services.Users.CreateIdentityAsync(user, context.Request.GetScopes());
Debug.Assert(identity != null);
if (identity == null) {
throw new InvalidOperationException("The token request failed because the user manager returned a null " +
$"identity for user '{await services.Users.GetUserNameAsync(user)}'.");
}
// Create a new authentication ticket holding the user identity.
var ticket = new AuthenticationTicket(

23
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs

@ -6,11 +6,13 @@
using System;
using System.Diagnostics;
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace OpenIddict.Infrastructure {
public partial class OpenIddictProvider<TUser, TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
@ -42,6 +44,9 @@ namespace OpenIddict.Infrastructure {
// Retrieve the application details corresponding to the requested client_id.
var application = await services.Applications.FindByIdAsync(context.ClientId);
if (application == null) {
services.Logger.LogError("The introspection request was rejected because the client " +
"application was not found: '{ClientId}'.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Application not found in the database: ensure that your client_id is correct.");
@ -52,6 +57,9 @@ namespace OpenIddict.Infrastructure {
// Reject non-confidential applications.
var type = await services.Applications.GetClientTypeAsync(application);
if (!string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase)) {
services.Logger.LogError("The introspection request was rejected because the public application " +
"'{ClientId}' was not allowed to use this endpoint.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Public applications are not allowed to use the introspection endpoint.");
@ -61,6 +69,9 @@ namespace OpenIddict.Infrastructure {
// Validate the client credentials.
if (!await services.Applications.ValidateSecretAsync(application, context.ClientSecret)) {
services.Logger.LogError("The introspection request was rejected because the confidential application " +
"'{ClientId}' didn't specify valid client credentials.", context.ClientId);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid credentials: ensure that you specified a correct client_secret.");
@ -74,11 +85,12 @@ namespace OpenIddict.Infrastructure {
public override async Task HandleIntrospectionRequest([NotNull] HandleIntrospectionRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
var principal = context.Ticket?.Principal;
Debug.Assert(principal != null);
var user = await services.Users.GetUserAsync(principal);
var user = await services.Users.GetUserAsync(context.Ticket.Principal);
if (user == null) {
services.Logger.LogInformation("The token {Identifier} was declared as inactive because the " +
"corresponding user ({Username}) was not found in the database.",
context.Ticket.GetTicketId(), services.Users.GetUserName(context.Ticket.Principal));
context.Active = false;
return;
@ -90,6 +102,9 @@ namespace OpenIddict.Infrastructure {
// if the corresponding entry cannot be found, return Active = false to indicate that is is no longer valid.
var token = await services.Tokens.FindByIdAsync(context.Ticket.GetTicketId());
if (token == null) {
services.Logger.LogInformation("The token {Identifier} was declared as inactive because " +
"it was revoked.", context.Ticket.GetTicketId());
context.Active = false;
return;

7
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs

@ -36,6 +36,9 @@ namespace OpenIddict.Infrastructure {
// cannot revoke a refresh token if it's not the intended audience,
// even if client authentication was skipped.
if (string.IsNullOrEmpty(context.ClientId)) {
services.Logger.LogInformation("The revocation request validation process was skipped " +
"because the client_id parameter was missing or empty.");
context.Skip();
return;
@ -97,7 +100,7 @@ namespace OpenIddict.Infrastructure {
return;
}
// Extract the token identifier from the authentication ticket.
// Extract the token identifier from the refresh token.
var identifier = context.Ticket.GetTicketId();
Debug.Assert(!string.IsNullOrEmpty(identifier),
"The refresh token should contain a ticket identifier.");
@ -116,7 +119,7 @@ namespace OpenIddict.Infrastructure {
// Revoke the refresh token.
await services.Tokens.RevokeAsync(token);
services.Logger.LogInformation("The refresh token '{Identifier}' was revoked.", identifier);
services.Logger.LogInformation("The refresh token '{Identifier}' was successfully revoked.", identifier);
context.Revoked = true;
}

6
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Serialization.cs

@ -26,9 +26,13 @@ namespace OpenIddict.Infrastructure {
Debug.Assert(!context.Request.IsClientCredentialsGrantType(),
"A refresh token should not be issued when using grant_type=client_credentials.");
// Note: a null value could be returned by FindByIdAsync if the user was removed after the initial
// check made by GrantAuthorizationCode/GrantRefreshToken/GrantResourceOwnerCredentials.
// In this case, throw an exception to abort the token request.
var user = await services.Users.FindByIdAsync(context.Ticket.Principal.GetClaim(ClaimTypes.NameIdentifier));
if (user == null) {
throw new InvalidOperationException("The user cannot be retrieved from the database.");
throw new InvalidOperationException("The token request was aborted because the user associated " +
"with the refresh token was not found in the database.");
}
string identifier;

32
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs

@ -11,6 +11,7 @@ using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace OpenIddict.Infrastructure {
public partial class OpenIddictProvider<TUser, TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
@ -21,6 +22,9 @@ namespace OpenIddict.Infrastructure {
// Skip validation if the optional post_logout_redirect_uri
// parameter was missing from the logout request.
if (string.IsNullOrEmpty(context.PostLogoutRedirectUri)) {
services.Logger.LogInformation("The logout request validation process was skipped because " +
"the post_logout_redirect_uri parameter was missing.");
context.Skip();
return;
@ -28,6 +32,10 @@ namespace OpenIddict.Infrastructure {
var application = await services.Applications.FindByLogoutRedirectUri(context.PostLogoutRedirectUri);
if (application == null) {
services.Logger.LogError("The logout request was rejected because the client application corresponding " +
"to the specified post_logout_redirect_uri was not found in the database: " +
"'{PostLogoutRedirectUri}'.", context.PostLogoutRedirectUri);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid post_logout_redirect_uri.");
@ -45,10 +53,15 @@ namespace OpenIddict.Infrastructure {
// If the authentication cookie doesn't exist or is no longer valid,
// the user agent is immediately redirected to the client application.
if (context.HttpContext.User.Identities.Any(identity => identity.IsAuthenticated)) {
// Ensure that the authentication cookie contains the required NameIdentifier claim.
// If it cannot be found, ignore the logout request and continue to the next middleware.
// Ensure that the authentication cookie contains the required ClaimTypes.NameIdentifier claim.
// If it cannot be found, don't handle the logout request at this stage and continue to the next middleware.
var identifier = context.HttpContext.User.GetClaim(ClaimTypes.NameIdentifier);
if (string.IsNullOrEmpty(identifier)) {
services.Logger.LogWarning("The logout request was not silently processed because the mandatory " +
"ClaimTypes.NameIdentifier claim was missing from the current principal.");
context.SkipToNextMiddleware();
return;
}
@ -57,19 +70,34 @@ namespace OpenIddict.Infrastructure {
// If the token cannot be extracted, don't handle the logout request at this stage and continue to the next middleware.
var principal = await context.HttpContext.Authentication.AuthenticateAsync(context.Options.AuthenticationScheme);
if (principal == null) {
services.Logger.LogInformation("The logout request was not silently processed because " +
"the id_token_hint parameter was missing or invalid.");
context.SkipToNextMiddleware();
return;
}
// Ensure that the identity token corresponds to the authenticated user. If the token cannot be
// validated, don't handle the logout request at this stage and continue to the next middleware.
if (!principal.HasClaim(ClaimTypes.NameIdentifier, identifier)) {
services.Logger.LogWarning("The logout request was not silently processed because the principal extracted " +
"from the id_token_hint parameter didn't correspond to the logged in user.");
context.SkipToNextMiddleware();
return;
}
services.Logger.LogInformation("The user '{Username}' was successfully logged out.",
services.Users.GetUserName(principal));
// Delete the ASP.NET Core Identity cookies.
await services.SignIn.SignOutAsync();
}
services.Logger.LogDebug("The logout request was silently processed without requiring user confirmation.");
// Redirect the user agent back to the client application.
await context.HttpContext.Authentication.SignOutAsync(context.Options.AuthenticationScheme);

19
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Userinfo.cs

@ -4,6 +4,7 @@
* the license and the contributors participating to this project.
*/
using System;
using System.Diagnostics;
using System.Security.Claims;
using System.Threading.Tasks;
@ -19,17 +20,13 @@ namespace OpenIddict.Infrastructure {
public override async Task HandleUserinfoRequest([NotNull] HandleUserinfoRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
var principal = context.Ticket?.Principal;
Debug.Assert(principal != null);
// Note: user may be null if the user has been removed.
// In this case, return a 400 response.
var user = await services.Users.GetUserAsync(principal);
// Note: user may be null if the user was removed after
// the initial check made by ValidateUserinfoRequest.
// In this case, throw an exception to abort the request.
var user = await services.Users.GetUserAsync(context.Ticket.Principal);
if (user == null) {
context.Response.StatusCode = 400;
context.HandleResponse();
return;
throw new InvalidOperationException("The userinfo request was aborted because the user profile " +
"corresponding to the access token was not found in the database.");
}
// Note: "sub" is a mandatory claim.
@ -39,7 +36,7 @@ namespace OpenIddict.Infrastructure {
// Only add the "preferred_username" claim if the "profile" scope was present in the access token.
// Note: filtering the username is not needed at this stage as OpenIddictController.Accept
// and OpenIddictProvider.GrantResourceOwnerCredentials are expected to reject requests that
// don't include the "email" scope if the username corresponds to the registed email address.
// don't include the "email" scope if the username corresponds to the registered email address.
if (context.Ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) {
context.PreferredUsername = await services.Users.GetUserNameAsync(user);

Loading…
Cancel
Save