-
Notifications
You must be signed in to change notification settings - Fork 10.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce WebApplicationBuilder.Authorization property that allows for AuthZ setup #42235
Comments
So basically the main difference from today is something like this?
vs today
|
Right, except that the second example actually has to be qualified to the builder, so: builder.Services.AddAuthorization(o =>
{
o.AddPolicy("MyPolicy", p =>
p.RequireAuthenticatedUser()
.RequireClaim("scope", "foo:bar"));
o.DefaultPolicy = o.GetPolicy("MyPolicy");
}) And of course // Both APIs top-level
builder.Authentication.UseJwtBearer();
builder.Authorization.AddPolicy(...);
// Split APIs
builder.Authentication.UseJwtBearer();
builder.Services.AddAuthorization(c => c.AddPolicy(...)); |
Sure would it be enough just to have a single Configure which just is shortening
|
Another benefit of the model is removing the nesting involved with having to call |
Right, one-less nested delegate, on top of one less property to discover/navigate, plus remove the slight dissonance of the |
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:
|
I'm not sure removing a single top level delegate is worth the overhead of adding 4 top level methods to the WebApplicationBuilder which is an alternative way to configure authorization that doesn't seem that much simpler. I'd rather see more effort going into simplifying whatever we think is complex about wiring up authorization policies themselves. |
@davidfowl it's not 4 methods on the |
Ah I see. Still, I'd rather have the a similar API that isn't tied to WebApplicationBuilder. IMHO this adds a property to make it more discoverable at the cost of having 2 APIs we'd have to document that do the exact same thing in different ways. |
Unfortunately we don't have the advantage of an existing builder type for AuthZ like we did for AuthN that meant we only needed to add a top-level property that exposed the already existing builder.Authentication.AddJwtBearer();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ApiReadAccess", policy =>
policy.RequireAuthenticatedUsers()
.RequireClaim("scope", "myapi:read"));
options.DefaultPolicy = options.GetPolicy("ApiReadAccess");
}); |
Okay, since we don't have the equivalent for an AuthorizationBuilder right now, I can take a stab at mocking something up for that as part of config stuff I'm prototyping right now, since I was looking for a place to hang methods other than extension methods on ServiceCollection |
@HaoK here's the hacky one I was using to explore this idea: public class WebApplicationAuthorizationBuilder
{
private readonly WebApplicationBuilder _builder;
public WebApplicationAuthorizationBuilder(WebApplicationBuilder builder)
{
_builder = builder;
}
public WebApplicationAuthorizationBuilder AddPolicy(
string name,
Action<AuthorizationPolicyBuilder> configurePolicy,
bool setAsDefault = false,
bool setAsFallback = false)
{
_builder.Services.AddAuthorization(authzOptions =>
{
var policyBuilder = new AuthorizationPolicyBuilder();
configurePolicy(policyBuilder);
var policy = policyBuilder.Build();
authzOptions.AddPolicy(name, policy);
if (setAsDefault)
{
authzOptions.DefaultPolicy = policy;
}
if (setAsFallback)
{
authzOptions.FallbackPolicy = policy;
}
});
return this;
}
public WebApplicationAuthorizationBuilder SetDefaultPolicy(string name)
{
_builder.Services.AddAuthorization(authzOptions =>
{
if (authzOptions.GetPolicy(name) is AuthorizationPolicy policy)
{
authzOptions.DefaultPolicy = policy;
}
else
{
throw new InvalidOperationException($"Can't find policy named '{name}'.");
}
});
return this;
}
public WebApplicationAuthorizationBuilder SetFallbackPolicy(string name)
{
_builder.Services.Configure<AuthorizationOptions>(authzOptions =>
{
if (authzOptions.GetPolicy(name) is AuthorizationPolicy policy)
{
authzOptions.FallbackPolicy = policy;
}
else
{
throw new InvalidOperationException($"Can't find policy named '{name}'.");
}
});
return this;
}
public WebApplicationAuthorizationBuilder Configure(Action<AuthorizationOptions> configure)
{
_builder.Services.Configure(configure);
return this;
}
}
public static class WebApplicationBuilderExtensions
{
private static readonly object _key = new();
public static WebApplicationAuthorizationBuilder Authorization(this WebApplicationBuilder builder)
{
if (builder.Host.Properties.TryGetValue(_key, out var value) && value is WebApplicationAuthorizationBuilder authzBuilder)
{
return authzBuilder;
}
if (value is { })
{
throw new InvalidOperationException("There's a different object living in our slot!");
}
authzBuilder = new WebApplicationAuthorizationBuilder(builder);
builder.Host.Properties.Add(_key, authzBuilder);
return authzBuilder;
}
} |
So if this is symmetrical to AuthenticationBuilder, we don't expose a generic Configure on it, since it already exposes the ServiceCollection What about something like this
EDIT: didn't realize the default/fallback require an instance but don't reqiure a name, so maybe we just have sugar methods for adding/setting default/fallback policy in addition to the normal instance sets. |
How would this be accessed via |
Unfortunately our AddAuthZ methods return IServiceCollection so unless we are going to do a breaking change I guess people just have to |
We can use this type on WebApplicationBuilder though right? WebApplicationBuilder.Authorization can expose this once we add it |
I think that's the thing that @davidfowl is pushing back on though, i.e. having a WAB-level way of doing something that doesn't match the services equivalent. Without getting past that we'd be locked into doing nothing here it seems. |
I don't think so, this is how we added builder to everything before. AuthenticationBuilder and OptionsBuilder were both sugar APIs we added ontop of IServiceCollection. AuthorizationBuilder follows that pattern, my read on the pushback is that it shouldn't be done at the WAB layer, this type of sugar belongs at the AuthZ layer. |
How do you get an AuthorizationBuilder? Seems we've already used the parameterless method here... |
Yeah this is the janky part. So |
If we add this though the intent would be to add/support it at both places, right, exactly like we did for |
Yeah that's my impression, we'd expose |
Right, so what I'm still not clear on is how it's exposed when working against |
Yeah that's what I was actually noodling around with right now (how to update the unit tests to use this new api). I mean this is the one I was looking at the canonical add policy
Breaking change:
Non breaking change (kinds gross)
Or
|
Ahh if only C# supported overloads on return types 😃 The breaking change is obviously the most natural looking but I really don't think we can justify making a break of that magnitude. The only other idea I can think of is another services extension overload that returns the builder, e.g.: builder.Services.AddAuthorization(useBuilder: true)
.AddPolicy(...); or builder.Services.AddAuthorizationWithBuilder()
.AddPolicy(...); |
Actually this is much easier than I realized, we just have AuthorizationBuilder implement IServiceCollection and proxy all calls into the service collection, so we get everything we want without breaking change |
So this: pros: no breaking change, cons: the full service collection API is exposed rather than scoped to authZ for intellesense
Or maybe we add two types:
|
I'm going to say we can't make a breaking change here, so I'd vote for the two types approach you outline, which would make the |
To make it super clear, these would be equivalent: // Off of WebApplicationBuilder, returning AuthorizationBuilder
builder.Authorization.AddPolicy(...);
// Off of IServiceCollection, returning AuthorizationBuilderServiceCollection
builder.Services.AddAuthorization()
.AddPolicy(...); |
Changing the return type of the method is breaking so we need to consider other patterns, e.g. a new method name or a new overload of |
Current leader is |
@DamianEdwards should I edit the description of this issue to use for API-review or do you want me to create a new issue? |
@HaoK let's just use this issue to cover it all |
@DamianEdwards updated comments with the current PR, can you double check this has everything you wanted? |
@HaoK looks good, marking ready for review. |
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:
|
API review notes:
letting David and co. discuss this one. namespace Microsoft.AspNetCore.Authorization;
public static class PolicyServiceCollectionExtensions
{
+ public static AuthorizationBuilder AddAuthorizationBuilder(this IServiceCollection services)
}
+public class AuthorizationBuilder
+{
+ AuthorizationBuilder(IServiceCollection services);
+ public virtual IServiceCollection { get; }
+ public virtual AuthorizationBuilder AddPolicy(
+ string name,
+ Action<AuthorizationPolicyBuilder> configurePolicy);
+ public virtual AuthorizationBuilder AddPolicy(
+ string name,
+ AuthorizationPolicy policy);
+ public virtual AuthorizationBuilder AddDefaultPolicy(
+ string name,
+ Action<AuthorizationPolicyBuilder> configurePolicy);
+ public virtual AuthorizationBuilder AddDefaultPolicy(
+ string name,
+ AuthorizationPolicy policy);
+ public virtual AuthorizationBuilder AddFallbackPolicy(
+ string name,
+ Action<AuthorizationPolicyBuilder> configurePolicy);
+ public virtual AuthorizationBuilder AddFallbackPolicy(
+ string name,
+ AuthorizationPolicy policy);
+ public virtual AuthorizationBuilder SetDefaultPolicy(string name);
+ public virtual AuthorizationBuilder SetFallbackPolicy(string name);
+ public virtual AuthorizationBuilder InvokeHandlersAfterFailure(bool invoke);
+} API approved! |
Done in #42264 |
Spin-off from #42172 which was a spin-off of #39855
Goal is to create a top-level (i.e.
WebApplicationBuilder
level) API for setting up AuthZ, mirroringWebApplicationBuilder.Authentication
and removing the need to have to find and usebuilder.Services.AddAuthorization()
orbuilder.Services.Configure<AuthorizationOptions>()
to setup global AuthZ options in an app.We will introduce a new
AuthorizationBuilder
class to be the public service area for AuthZ going forward (similar to AuthenticationBuilder for AuthN). It will also be accessible via a new IServiceCollcetion extension methodAddAuthorizationBuilder()
since we cannot changeAddAuthorization()
to return this without a breaking changeProposed new API:
The text was updated successfully, but these errors were encountered: