Description
Background and Motivation
In many applications, claims-based authorization is a critical component for enforcing security and access controls. The current implementation of AuthorizationPolicyBuilder.RequireClaim
(and ClaimsAuthorizationRequirement
) is limited to checking claims based on a specific type and value. This restriction can force developers to implement custom claim-checking logic outside the provided methods.
Consider the following scenarios:
- Pattern Matching: Verifying if a claim value matches a specific pattern or contains a substring.
- Complex Conditions: Checking for claims where multiple properties need to be considered, such as both type and issuer.
These scenarios highlight the need for a more flexible claim-checking mechanism.
By adding an overload that accepts a Func<Claim, bool>
, these kinds of scenarios are easier to implement without additional custom code.
Proposed API
namespace Microsoft.AspNetCore.Authorization;
public partial class AuthorizationPolicyBuilder
{
// existing
public AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] allowedValues);
public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable<string> allowedValues);
public AuthorizationPolicyBuilder RequireClaim(string claimType);
// new
+ public AuthorizationPolicyBuilder RequireClaim(Func<Claim, bool> match); // alternative name: claimPredicate
}
namespace Microsoft.AspNetCore.Authorization.Infrastructure;
public partial class ClaimsAuthorizationRequirement : AuthorizationHandler<ClaimsAuthorizationRequirement>, IAuthorizationRequirement
{
// existing
public ClaimsAuthorizationRequirement(string claimType, IEnumerable<string>? allowedValues)
// new
+ public ClaimsAuthorizationRequirement(Func<Claim, bool> match) // alternative name: claimPredicate
// ClaimType can now be null if Func<Claim, bool> is provided
- public string ClaimType { get; }
+ public string? ClaimType { get; }
// can also be a private field but considering that ClaimType and AllowedValues are exposed, maybe this value should also be exposed.
+ public Func<Claim, bool>? Match { get; } // alternative name: ClaimPredicate
}
This proposal matches the method ClaimIdentity.HasClaim(Predicate<System.Security.Claims.Claim> match)
that already exists in the BCL. I use Func<T, bool>
as I saw recent API proposals in the runtime repo recommended to use this instead of Predicate<T>
(e.g. dotnet/runtime#107962 (comment)).
Usage Examples
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(options =>
{
// Checking if there is any claim that starts with a certain prefix
options.AddPolicy("prefix", policy => policy.RequireClaim(claim => claim.Value.StartsWith("prefix-"));
// Checking if there is a claim with type "role" and value containing "admin"
options.AddPolicy("admin", policy => policy.RequireClaim(claim => claim.Type == "role" && claim.Value.Contains("admin"));
});
Risks
Nothing I can see now.