Skip to content
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

Proposal for how we handle more metadata for auth #39892

Closed
wants to merge 2 commits into from

Conversation

davidfowl
Copy link
Member

@davidfowl davidfowl commented Jan 31, 2022

  • The user can specify additional requirements or policies on the endpoint and they will be combined with existing IAuthorizeData.

Contributes to #39840

This is what I was thinking the change could look like in all of the places that currently call into the auth system.

Some open questions:

  • In this change you need to use the Authorize attribute still. It's not enough to have a policy on the endpoint. This is sheer laziness but it also means we would need to detect policies on the endpoint execution middleware (
    if (endpoint.Metadata.GetMetadata<IAuthorizeData>() != null &&
    ).
  • Same idea for requirements, they can augment the authorize attribute on the endpoint. This one is trickier because if you want to make sure requirements apply to a specific set of auth schemes then you need to use an authorize attribute as well to specify that.

cc @HaoK @pranavkm @BrennanConroy @Tratcher

- The user can specify additional requirements or policies on the endpoint and they will be combined with existing IAuthorizeData.
@ghost ghost added the area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer label Jan 31, 2022
/// <returns>
/// A new <see cref="AuthorizationPolicy"/> which represents the combination of the
/// authorization policies provided by the specified <paramref name="policyProvider"/>.
/// </returns>
public static async Task<AuthorizationPolicy?> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData)
public static async Task<AuthorizationPolicy?> CombineAsync(IAuthorizationPolicyProvider policyProvider,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we care at all potential duplicate requirements in here? I guess currently we don't try to dedupe anything when we combine

This is also complicated enough that I wonder if this is worth exposing preemptively as some kind of IAuthorizationPolicyCombiner now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IAuthorizationPolicyCombiner - No more abstractions 😄. I think the policy combining logic is actually pretty simple and I think we should de-dupe (actually if you have N authorize attributes you end up with the default policy added N times, but the system dedupes elsewhere).

@davidfowl
Copy link
Member Author

Also side note: It's unfortunate we do all of this work per request. This could be done and cached on the endpoint once.

var policies = endpoint?.Metadata.GetOrderedMetadata<AuthorizationPolicy>() ?? Array.Empty<AuthorizationPolicy>();
var reqirements = endpoint?.Metadata.GetOrderedMetadata<IAuthorizationRequirement>() ?? Array.Empty<IAuthorizationRequirement>();

var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData, policies, reqirements);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we cache the policy it on Endpoint as an internal property (assuming _policyProvider is a default one)? This seems like to quite a bit to have to do on a per-request basis.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this PR but if you figure out a way to store objects per endpoint, this PR (#21675) could probably be revived too.

public static async Task<AuthorizationPolicy?> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData)
public static Task<AuthorizationPolicy?> CombineAsync(IAuthorizationPolicyProvider policyProvider,
IEnumerable<IAuthorizeData> authorizeData) => CombineAsync(policyProvider, authorizeData,
Enumerable.Empty<AuthorizationPolicy>(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it's better to use Array.Empty here since it would benefit from a slightly faster Enumerable.Any()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only slightly in since Enumerable.Empty<T> is also optimized for the Count check.

/// <param name="policyProvider">A <see cref="IAuthorizationPolicyProvider"/> which provides the policies to combine.</param>
/// <param name="authorizeData">A collection of authorization data used to apply authorization to a resource.</param>
/// <param name="policies">A collection of <see cref="AuthorizationPolicy"/> policies to combine.</param>
/// <param name="requirements">A collection of <see cref="IAuthorizationRequirement"/>s to add to the auth policy.</param>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// <param name="requirements">A collection of <see cref="IAuthorizationRequirement"/>s to add to the auth policy.</param>
/// <param name="requirements">A collection of <see cref="IAuthorizationRequirement"/> instances to add to the policy.</param>

@davidfowl
Copy link
Member Author

davidfowl commented Feb 2, 2022

Any other thoughts on this @HaoK @Tratcher @blowdart? I'll expand this to work in other places to get a since for how it would look there.

@HaoK
Copy link
Member

HaoK commented Feb 2, 2022

This looks nice so far. Does this make it any better in any situation now that you can specifying authentication schemes in the Policies this way as well? I guess now you can have pure AuthZ policies that don't specify any authentication schemes, and just put metadata on the endpoint that says whether it takes jwt or cookies, which maybe is a little better than before

{
services.AddSingleton<IAuthorizationHandler, CustomAuthHandler>();
},
endpoint: CreateEndpoint(new AuthorizeAttribute(), new CustomRequirement("This")));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what happens here when you combine what's used to be a default policy situation of an empty authorize attribute and a custom requirement, it kind of looks like we wouldn't use the default policy, and would just use the custom requirement, which I think might be not what we want, since we'd lose the requirement of needing an authenticated user right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I wanted to discuss. It seems like to don't use the default policy if one is already specified so I mimicked that approach here as well. Maybe that only applies when you have a custom policy but additional requirements don't affect that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think everyone just assumes any kind of authorize brings along that RequireAuthenticatedUser that gets combined for free today. As long as we don't make them have to explicitly add it in any of these new usage scenarios it should be fine.

@davidfowl
Copy link
Member Author

This looks nice so far. Does this make it any better in any situation now that you can specifying authentication schemes in the Policies this way as well? I guess now you can have pure AuthZ policies that don't specify any authentication schemes, and just put metadata on the endpoint that says whether it takes jwt or cookies, which maybe is a little better than before

Right so I guess you can do:

  1. [Authorize], Default Policy
  2. [Authorize(Policy, Schemes)]
  3. [Authorize], Default Policy, Additional requirements from metadata
  4. [Authorize(Schemes)], Default Policy, Additional requirements from metadata
  5. [Authorize], Additional policies from metadata (overrides the default)
  6. [Authorize(Schemes)], Additional policies from metadata (combine everything like multiple auth attributes)

@HaoK
Copy link
Member

HaoK commented Feb 3, 2022

So do we need to give them both the ability to define policies and requirements? Does it simplify anything if we only give them the ability to define additional policies basically we lose 3. and 4. Its only slightly more work to shove your requirements into an empty policy

@davidfowl
Copy link
Member Author

The problem is that policies aren't easily defined directly on the endpoint outside of minimal APIs. The only way to declaratively define a policy is the way we do today. I think 3 is a critical feature but it also worries me that you need both IAuthorizeData and the additional requirements. Feels like a recipe for security mistakes since you need both things.

@davidfowl
Copy link
Member Author

Thinking more about it, it would be possible to push this responsibility into the frameworks (creating a policy from requirements) but it would always override the default policy....

@HaoK
Copy link
Member

HaoK commented Feb 3, 2022

I mean we could always add a DefaultPolicyRequirement, which they could use to signal ask to get the combination. I think its simpler for the metadata to be limited to asking for more schemes and requirements, leave packaging/combining things to the frameworks, there's no notion of polices in the lowest level of IAuthorizationService anyways, it's always just been a unit of moving metadata around. They can already easily augment with policies today by just adding [Authorize(Policy)} right? So you can get 3,4 behavior just by augmenting via the special DefaultPolicyRequirement, or some other flag

@HaoK
Copy link
Member

HaoK commented Apr 1, 2022

@blowdart any thoughts about this before this starts getting real?

@blowdart
Copy link
Contributor

blowdart commented Apr 1, 2022

I suggest talking to Jiachen who is doing user research into what users want, before assuming any suggested API changes help.

@HaoK
Copy link
Member

HaoK commented Apr 1, 2022

The motivation behind this directly came out of some user feedback from the team building our docs site who had to do a lot of complex string parsing in their policy providers, which lead to focusing on making it easier to flow metadata from the app (authorize attribute), to the AuthZ handlers, see #39840

Users have been expressing what they want here for several years now, this PR is where we try to design something nicer as we always knew the PolicyProvider that only allowed the name of the policy as the communication was just a first step/workaround

@davidfowl davidfowl closed this Apr 19, 2022
@davidfowl davidfowl deleted the davidfowl/authz-extrametadata branch April 24, 2022 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants