Improve custom IAuthorizeData with IAuthorizationRequirementsProvider
See original GitHub issueCurrently, in AspNetCore 3.1, the middleware AuthorizationMiddleware
calls the AuthorizationPolicy.CombineAsync(IAuthorizationPoliceProvider, IEnumerable<IAuthorizeData>)
static method to build a unified authorization policy that combines the rules defined in the authorization metadata of instance type IAuthorizeData
(ie: AuthorizeAttribute
). All the rules translated from the IAuthorizeData
metadata is made by this method.
In a simple way, the CombineAsync
method:
- combines the policies defined by
IAuthorizeData.Policy
property; - adds a
RolesAuthorizationRequirement
instance to this unified policy if theIAuthorizeData.Roles
property is set; - combines the defined authorization schemes by the
IAuthorizeData.AuthorizationSchemes
property.
The policy-based authorization approach aggregates the policy validation rules by a set of IAuthorizationRequirement
objects. So, to improve that approach and to facilitate the custom implementations of IAuthorizeData
types, I suggest some aspects that can benefit the implementation of authorization in AspNetCore.
-
Provide an interface with the following contract:
interface IAuthorizationRequirementsProvider { Task<IEnumerable<IAuthorizationRequirement>> GetRequirementsAsync(IAuthorizeData authorizeData); }
That’s interface method takes an
IAuthorizeData
instance and returns theIAuthorizationRequirement
instances relevant to your authorization logic.- Multiple
IAuthorizationRequirementsProvider
providers can be registered in the service container. - Two default providers will be made available and registered by the framework:
-
RolesAuthorizationRequirementsProvider
: which implements the current logic inCombineAsync
; -
PassThroughAuthorizationRequirementsProvider
: which behaves similarly to thePassThroughAuthorizationHandler
class:async Task<IEnumerable<IAuthorizationRequirement>> GetRequirementsAsync(IAuthorizeData authorizeData) { if (authorizeData is IAuthorizationRequirementsProvider requirementsProvider) { return await requirementsProvider.GetRequirementsAsync(authorizeData); } if (authorizeData is IAuthorizationRequirement requirement) { return new[] { requirement }; } return await AuthorizationRequirementsProvider.EmptyResult; }
-
- Multiple
-
The
CombineAsync
method will be relocated to theIAuthorizationPolicyProvider
interface, with the signatureTask<AuthorizationPolicy> CreatePolicyAsync(IEnumerable<IAuthorizeData> authorizeData)
.-
The
CreatePolicyAsync
code, implemented inDefaultAuthorizationPolicyProvider
, is the same asCombineAsync
, with a slight difference:- The code block:
var rolesSplit = authorizeDatum.Roles?.Split(','); if (rolesSplit?.Length > 0) { var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim()); policyBuilder.RequireRole(trimmedRolesSplit); useDefaultPolicy = false; }
- Becomes:
// _requirementsProviders: IEnumerable<IAuthorizationRequirementsProvider> // injected in constructor foreach (var requirementsProvider in _requirementsProviders) { var requirements = await requirementsProvider.GetRequirementsAsync(authorizeDatum); var requirementsArray = requirements as IAuthorizationRequirement[] ?? requirements.ToArray(); if (requirementsArray.Length > 0) { useDefaultPolicy = false; } policyBuilder.AddRequirements(requirementsArray); }
CombineAsync
now calls:
return await policyProvider.CreatePolicyAsync(authorizeData);
CombineAsync
is marked as obsolete. In AspNetCore 5, this method can be removed.
- The code block:
-
A sample case:
public class SampleAuthorizeAttribute : AuthorizeAttribute, IAuthorizationRequirementsProvider
{
/// <summary>
/// Gets or sets the scope allowed to access the resource.
/// </summary>
string Scope { get; set; }
public Task<IEnumerable<IAuthorizationRequirement>> GetRequirementsAsync(IAuthorizeData authorizeData)
{
if (Scope == null)
return AuthorizationRequirementsProvider.EmptyResult;
return AuthorizationRequirementsProvider.Result(
new ClaimsAuthorizationRequirement("scope", new[] { Scope })
);
}
}
Other implementations combining IAuthorizationRequirement
and IAuthorizationHandler
are covered.
For more details, see the affected code at: https://github.com/rodrigo-speller/aspnetcore/tree/authorization-requirements-provider/src/Security/Authorization/Core/src
- AuthorizationPolicy.cs
- AuthorizationRequirementsProvider.cs
- AuthorizationServiceCollectionExtensions.cs
- DefaultAuthorizationPolicyProvider.cs
- IAuthorizationPolicyProvider.cs
- IAuthorizationRequirementsProvider.cs
- PassThroughAuthorizationRequirementsProvider.cs
- RolesAuthorizationRequirementsProvider.cs
There are no breaking changes.
I’m requesting for comments before “pull request”.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:5 (3 by maintainers)
I’m putting this into discussion, because 5 is pretty much done
Convincing @blowdart won’t be easy. We have already tried… https://github.com/aspnet/Security/issues/1689 😄