Description
Relates to #34545
We should allow the definition and/or application of authorization policies to specific endpoints via endpoint metadata at the time they're declared. This will make configuration of resource authorization for Minimal API style applications much simpler and more inline with the principals of Minimal APIs while still enabling re-use of policies via language features rather than relying on their definition at the time authorization is added to DI.
The AuthorizationMiddleware
would be updated to retrieve metadata for the current request and ensure any instances that implement IAuthorizationRequirement
are passed to the IAuthorizationService
for evaluation (e.g. as IAuthorizationHandler
or IAuthorizeData
, etc.).
New extension methods would be added to enable setting AuthorizationPolicy
on endpoint definitions, as well as methods for setting IAuthorizationRequirement
, IAuthorizationHandler
or IAuthorizeData
as metadata:
// Set authorization metadata via an instance of AuthorizationPolicy
public static TBuilder RequireAuthorization<TBuilder>(this TBuilder builder, AuthorizationPolicy policy) where TBuilder : IEndpointConventionBuilder;
// Set authorization metadata via a callback accepting Action<AuthorizationPolicyBuilder>
public static TBuilder RequireAuthorization<TBuilder>(this TBuilder builder, Action<AuthorizationPolicyBuilder> configurePolicy) where TBuilder : IEndpointConventionBuilder;
// Set authoriziation metadata via an instance of IAuthorizationRequirement
public static TBuilder RequireAuthorization<TBuilder>(this TBuilder builder, IAuthorizationRequirement authoriziationRequirement) where TBuilder : IEndpointConventionBuilder;
// Set authoriziation metadata via an instance of IAuthorizationHandler
public static TBuilder RequireAuthorization<TBuilder>(this TBuilder builder, IAuthorizationHandler authoriziationHandler) where TBuilder : IEndpointConventionBuilder;
// Set authoriziation metadata via an instance of IAuthorizeData
public static TBuilder RequireAuthorization<TBuilder>(this TBuilder builder, IAuthorizeData authorizeData) where TBuilder : IEndpointConventionBuilder;
Example usage:
...
app.UseAuthentication();
app.UseAuthorization();
// Create and use a policy on multiple endpoints
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireClaim("some:claim", "this-value")
.Build();
app.MapGet("/protected", () => "you are allowed!")
.RequireAuthorization(policy);
app.MapGet("/also-protected", () => "you are allowed!")
.RequireAuthorization(policy);
// Create and pass a policy inline to the endpoint definition
app.MapGet("/fowlers-only-policy", () => "you are allowed!")
.RequireAuthorization(new AuthorizationPolicyBuilder().RequireUserName("Fowler").Build());
// Define a policy directly on the endpoint via a callback accepting Action<AuthorizationPolicyBuilder>
app.MapGet("/fowlers-only-builder", () => "you are allowed!")
.RequireAuthorization(p => p.RequireUserName("Fowler"));
// Use a custom attribute that implements IAuthorizationRequirement and IAuthorizationHandler to allow declarative metadata based authorization
app.MapGet("/fowlers-only-attribute", [RequiresUsername("Fowler")] () =>"you are allowed!");
// Use the attribute imperatively
app.MapGet("/fowlers-only-inline", () => "you are allowed!")
.RequireAuthorization(new RequiresUsernameAttribute("Fowler"));
...
public class RequiresUsernameAttribute : Attribute, IAuthorizationHandler, IAuthorizationRequirement
{
public RequiresUsernameAttribute(string username)
{
Username = username;
}
public string Username { get; set; }
public Task HandleAsync(AuthorizationHandlerContext context)
{
if (context.User.Identity?.Name == Username)
{
context.Succeed(this);
}
return Task.CompletedTask;
}
}
Activity
ghost commentedon Jan 28, 2022
Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:
BrennanConroy commentedon Jan 28, 2022
We have this one already
aspnetcore/src/Security/Authorization/Policy/src/AuthorizationEndpointConventionBuilderExtensions.cs
Line 60 in d4e70bd
davidfowl commentedon Jan 28, 2022
@BrennanConroy to make the attribute work with SignalR, we need to pass the hub method metadata to the auth system so that it can invoke these attributes.
@DamianEdwards I'm not yet sure if we need to make changes to the authZ APIs to allow passing the endpoint metadata as requirements or if the caller needs to do that manually.
HaoK commentedon Jan 28, 2022
Should we add an example of what a more complicated Permissions system would look like to see how much better this is now that the handler has access to the Attribute data? i.e. Policy = "Read" / "Write" / "Delete" can become
new RequirePermission(Permissions.Read | Write | Delete)
right?davidfowl commentedon Jan 28, 2022
Yes! @HaoK, exactly. So something like this:
HaoK commentedon Jan 28, 2022
So the one caveat is that the current limitation with the instance based Handler/Requirements, is you lose the ability to inject as easily, which is something worth mentioning up front, so maybe its worth updating the permission example to demonstrate how they'd get the DbContext from the request and pass it through to the GetPermissionsForUser call to make it more 'real'. Worse comes to worse they can probably always just service locate off the request in the handler context
davidfowl commentedon Jan 28, 2022
Here's what I was thinking because this issue doesn't quite do it justice:
To summarize what feature we're adding here so it's clear:
This extends how and where you get to define authorization requirements. Today the only way to use this list of requirement is via a policy name. The policy name maps to a list of requirements and a list of authentication schemes. So there's this indirection that stops you from defining data on your resource.
Before
After
This lets you define the requirement directly on the resource and the job of the framework is to pass this to the authZ system. This lets handlers do their thing and work as they do today. The attribute here is using the trick where the requirement can also be a handler but it doesn't require that.
DamianEdwards commentedon Jan 28, 2022
Note you still need to call
builder.Services.AddAuthorization()
andapp.UseAuthorization()
right now (in your "After" sample) but we're going to look at ways to potentially improve that separately.ghost commentedon Jan 28, 2022
Thanks for contacting us.
We're moving this issue to the
.NET 7 Planning
milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.
34 remaining items
HaoK commentedon Jul 7, 2022
I'm using this to track the authz caching work discussed to speed up the hot path so endpoints don't combine authz policies every time
HaoK commentedon Aug 15, 2022
Cache work done in #43124