Browse Source

Reject authorization requests that specify code_challenge_method=plain or use an inappropriate response type

pull/183/head
Kévin Chalet 10 years ago
parent
commit
76d9b62571
  1. 54
      src/OpenIddict.Core/Infrastructure/OpenIddictHelpers.cs
  2. 60
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs
  3. 6
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Discovery.cs
  4. 3
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Exchange.cs
  5. 3
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs
  6. 3
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Revocation.cs

54
src/OpenIddict.Core/Infrastructure/OpenIddictHelpers.cs

@ -12,6 +12,14 @@ using Microsoft.AspNetCore.Identity;
namespace OpenIddict.Infrastructure { namespace OpenIddict.Infrastructure {
public static class OpenIddictHelpers { public static class OpenIddictHelpers {
/// <summary>
/// Tries to find the given claim in the user claims.
/// </summary>
/// <typeparam name="TUser">The type of the User entity.</typeparam>
/// <param name="manager">The user manager.</param>
/// <param name="user">The user.</param>
/// <param name="type">The claim type.</param>
/// <returns>The claim value, or <c>null</c> if it cannot be found.</returns>
public static async Task<string> FindClaimAsync<TUser>( public static async Task<string> FindClaimAsync<TUser>(
[NotNull] this UserManager<TUser> manager, [NotNull] this UserManager<TUser> manager,
[NotNull] TUser user, [NotNull] string type) where TUser : class { [NotNull] TUser user, [NotNull] string type) where TUser : class {
@ -33,5 +41,51 @@ namespace OpenIddict.Infrastructure {
where string.Equals(claim.Type, type, StringComparison.OrdinalIgnoreCase) where string.Equals(claim.Type, type, StringComparison.OrdinalIgnoreCase)
select claim.Value).FirstOrDefault(); select claim.Value).FirstOrDefault();
} }
/// <summary>
/// Determines whether an application is a confidential client.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <param name="manager">The application manager.</param>
/// <param name="application">The application.</param>
/// <returns><c>true</c> if the application is a confidential client, <c>false</c> otherwise.</returns>
public static async Task<bool> IsConfidentialAsync<TApplication>(
[NotNull] this OpenIddictApplicationManager<TApplication> manager,
[NotNull] TApplication application) where TApplication : class {
if (manager == null) {
throw new ArgumentNullException(nameof(manager));
}
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
var type = await manager.GetClientTypeAsync(application);
return string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Determines whether an application is a public client.
/// </summary>
/// <typeparam name="TApplication">The type of the Application entity.</typeparam>
/// <param name="manager">The application manager.</param>
/// <param name="application">The application.</param>
/// <returns><c>true</c> if the application is a public client, <c>false</c> otherwise.</returns>
public static async Task<bool> IsPublicAsync<TApplication>(
[NotNull] this OpenIddictApplicationManager<TApplication> manager,
[NotNull] TApplication application) where TApplication : class {
if (manager == null) {
throw new ArgumentNullException(nameof(manager));
}
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
var type = await manager.GetClientTypeAsync(application);
return string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase);
}
} }
} }

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

@ -139,6 +139,16 @@ namespace OpenIddict.Infrastructure {
return; return;
} }
// Reject authorization requests that specify scope=offline_access if the refresh token flow is not enabled.
if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) &&
!services.Options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken)) {
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "The 'offline_access' scope is not allowed.");
return;
}
// Note: the OpenID Connect server middleware supports the query, form_post and fragment response modes // Note: the OpenID Connect server middleware supports the query, form_post and fragment response modes
// and doesn't reject unknown/custom modes until the ApplyAuthorizationResponse event is invoked. // and doesn't reject unknown/custom modes until the ApplyAuthorizationResponse event is invoked.
// To ensure authorization requests are rejected early enough, an additional check is made by OpenIddict. // To ensure authorization requests are rejected early enough, an additional check is made by OpenIddict.
@ -167,14 +177,46 @@ namespace OpenIddict.Infrastructure {
return; return;
} }
// Reject authorization requests that specify scope=offline_access if the refresh token flow is not enabled. // Note: the OpenID Connect server middleware always ensures a
if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && // code_challenge_method can't be specified without code_challenge.
!services.Options.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken)) { if (!string.IsNullOrEmpty(context.Request.CodeChallenge)) {
context.Reject( // Since the default challenge method (plain) is explicitly disallowed,
error: OpenIdConnectConstants.Errors.InvalidRequest, // reject the authorization request if the code_challenge_method is missing.
description: "The 'offline_access' scope is not allowed."); if (string.IsNullOrEmpty(context.Request.CodeChallengeMethod)) {
services.Logger.LogError("The authorization request was rejected because the " +
"required 'code_challenge_method' parameter was missing.");
return; context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "The 'code_challenge_method' parameter must be specified.");
return;
}
// Disallow the use of the unsecure code_challenge_method=plain method.
// See https://tools.ietf.org/html/rfc7636#section-7.2 for more information.
if (context.Request.CodeChallengeMethod == OpenIdConnectConstants.CodeChallengeMethods.Plain) {
services.Logger.LogError("The authorization request was rejected because the " +
"'code_challenge_method' parameter was set to 'plain'.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "The specified response_type parameter is not allowed when using PKCE.");
return;
}
// Reject authorization requests that contain response_type=token when a code_challenge is specified.
if (context.Request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token)) {
services.Logger.LogError("The authorization request was rejected because the " +
"specified response type was not compatible with PKCE.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "The specified response_type parameter is not allowed when using PKCE.");
return;
}
} }
// Retrieve the application details corresponding to the requested client_id. // Retrieve the application details corresponding to the requested client_id.
@ -205,9 +247,7 @@ namespace OpenIddict.Infrastructure {
// flow are rejected if the client identifier corresponds to a confidential application. // flow are rejected if the client identifier corresponds to a confidential application.
// Note: when using the authorization code grant, ValidateTokenRequest is responsible of // Note: when using the authorization code grant, ValidateTokenRequest is responsible of
// rejecting the token request if the client_id corresponds to an unauthenticated confidential client. // rejecting the token request if the client_id corresponds to an unauthenticated confidential client.
var type = await services.Applications.GetClientTypeAsync(application); if (await services.Applications.IsPublicAsync(application) && !context.Request.IsAuthorizationCodeFlow()) {
if (!string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase) &&
!context.Request.IsAuthorizationCodeFlow()) {
context.Reject( context.Reject(
error: OpenIdConnectConstants.Errors.InvalidRequest, error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Confidential clients can only use response_type=code."); description: "Confidential clients can only use response_type=code.");

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

@ -17,7 +17,11 @@ namespace OpenIddict.Infrastructure {
public override Task HandleConfigurationRequest([NotNull] HandleConfigurationRequestContext context) { public override Task HandleConfigurationRequest([NotNull] HandleConfigurationRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>(); var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TUser, TApplication, TAuthorization, TScope, TToken>>();
Debug.Assert(services.Options.GrantTypes.Count != 0, "At least one flow should be enabled."); // Note: though it's natively supported by the OpenID Connect server middleware,
// OpenIddict disallows the use of the unsecure code_challenge_method=plain method,
// which must be manually removed from the code_challenge_methods_supported property.
// See https://tools.ietf.org/html/rfc7636#section-7.2 for more information.
context.CodeChallengeMethods.Remove(OpenIdConnectConstants.CodeChallengeMethods.Plain);
// Note: the OpenID Connect server middleware automatically populates grant_types_supported // Note: the OpenID Connect server middleware automatically populates grant_types_supported
// by determining whether the authorization and token endpoints are enabled or not but // by determining whether the authorization and token endpoints are enabled or not but

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

@ -161,8 +161,7 @@ namespace OpenIddict.Infrastructure {
return; return;
} }
var type = await services.Applications.GetClientTypeAsync(application); if (await services.Applications.IsPublicAsync(application)) {
if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) {
// Note: public applications are not allowed to use the client credentials grant. // Note: public applications are not allowed to use the client credentials grant.
if (context.Request.IsClientCredentialsGrantType()) { if (context.Request.IsClientCredentialsGrantType()) {
services.Logger.LogError("The token request was rejected because the public client application '{ClientId}' " + services.Logger.LogError("The token request was rejected because the public client application '{ClientId}' " +

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

@ -55,8 +55,7 @@ namespace OpenIddict.Infrastructure {
} }
// Reject non-confidential applications. // Reject non-confidential applications.
var type = await services.Applications.GetClientTypeAsync(application); if (!await services.Applications.IsConfidentialAsync(application)) {
if (!string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase)) {
services.Logger.LogError("The introspection request was rejected because the public application " + services.Logger.LogError("The introspection request was rejected because the public application " +
"'{ClientId}' was not allowed to use this endpoint.", context.ClientId); "'{ClientId}' was not allowed to use this endpoint.", context.ClientId);

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

@ -55,8 +55,7 @@ namespace OpenIddict.Infrastructure {
} }
// Reject revocation requests containing a client_secret if the client application is not confidential. // Reject revocation requests containing a client_secret if the client application is not confidential.
var type = await services.Applications.GetClientTypeAsync(application); if (await services.Applications.IsPublicAsync(application)) {
if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) {
// Reject tokens requests containing a client_secret when the client is a public application. // Reject tokens requests containing a client_secret when the client is a public application.
if (!string.IsNullOrEmpty(context.ClientSecret)) { if (!string.IsNullOrEmpty(context.ClientSecret)) {
context.Reject( context.Reject(

Loading…
Cancel
Save