We can apply custom authorization policys using IAuthorizationPolicyProvider from Microsoft.AspNetCore.Authorization; with the following flow.
Create /Authorization/Requirements/ApiKeyRequirement.cs where ApiKey is the business use case. This could be anything that is sensible for your use case. EG: AdminUser
IAuthorizationRequirement is a marker interface with no methods and the mechanism for tracing whether authorization is successful.
You can however include state in the requirements construction, the ApiKeyRequirement instance will be passed into the handler so you would then access it as requirement.Permission.
Create /Authorization/AuthorizationRequirementMapper.cs. This will hold a collection of key/values being string and IAuthorizationRequirement and methods to access these. Policys is just a static class with static string members.
namespaceSweetApp.Authorization { publicclassAuthorizationRequirementMapper : IAuthorizationRequirementMapper { ///<inheritdoc/> public IDictionary<string, IAuthorizationRequirement> GetAuthorizationRequirementMappings() { returnnew Dictionary<string, IAuthorizationRequirement> { { Policys.FallbackRequirementPolicy, new FallbackRequirement() }, { Policys.RequireHeaderKeyPolicy, new ApiKeyRequirement() }, { Policys.DefaultPolicy, new DefaultPolicyRequirement() }, }; }
public IAuthorizationRequirement GetDefaultPolicy() { return GetAuthorizationRequirementMappings()[Policys.DefaultPolicy]; }
///<inheritdoc/> public IAuthorizationRequirement GetFallbackPolicy() { return GetAuthorizationRequirementMappings()[Policys.FallbackRequirementPolicy]; } } }
Create /Authorization/AuthorizationPolicyProvider.cs then inherit and implement IAuthorizationPolicyProvider. This will give you GetDefaultPolicyAsync(), GetFallbackPolicyAsync() and GetPolicyAsync(policyName).
///<summary> /// GetDefaultPolicyAsync returns the default authorization policy (the policy used for [Authorize] attributes without a policy specified). ///</summary> ///<returns></returns> public Task<AuthorizationPolicy> GetDefaultPolicyAsync() { var requirement = _authorizationRequirementMapper .GetDefaultPolicy();
return GetPolicy(requirement); }
///<summary> /// GetFallbackPolicyAsync returns the fallback authorization policy (the policy used by the Authorization Middleware when no policy is specified). ///</summary> ///<returns></returns> public Task<AuthorizationPolicy> GetFallbackPolicyAsync() { var requirement = _authorizationRequirementMapper .GetFallbackPolicy();
return GetPolicy(requirement); }
///<summary> /// GetPolicyAsync returns an authorization policy for a given name. Example `[Authorize(Policy = "RequireHeaderKeyPolicy")]` ///</summary> ///<param name="policyName"></param> ///<returns></returns> public Task<AuthorizationPolicy> GetPolicyAsync(string policyName) { if (_authorizationRequirementMapper .GetAuthorizationRequirementMappings() .TryGetValue(policyName, out IAuthorizationRequirement requirement)) { return GetPolicy(requirement); }
return GetDefaultPolicyAsync(); }
private Task<AuthorizationPolicy> GetPolicy(IAuthorizationRequirement requirement) { var policy = new AuthorizationPolicyBuilder() .AddRequirements(requirement) .Build();
return Task.FromResult(policy); } } }
Add the authorization mapper, policy provider and handlers to the application pipeline in Startup.cs
So now the application has 3 authorization options
5.1 GetFallbackPolicyAsync The policy run for all end points with no authorization annotation. The value could be that it now replaces [Authorize] attribute on all controllers. Note that if you do this, for end points that dont require authorization checks you now need to add the [AllowAnonymous] annotation. This would be FallbackRequirementHandler
5.2 GetDefaultPolicyAsync The policy run for end points with the annotation [Authorize].
5.3 GetPolicyAsync The policy run for end points with the policy name specified in the annotation [Authorize(Policy = "RequireHeaderKeyPolicy")].