microsoft-identity-web icon indicating copy to clipboard operation
microsoft-identity-web copied to clipboard

[Question] RequiredScope doesn't work (for web APIs called by daemon apps)

Open Anakael opened this issue 3 years ago • 15 comments

Which version of Microsoft Identity Web are you using?

1.21.1

Where is the issue?

  • Web API
    • [ ] Protected web APIs (validating scopes)

Is this a new or an existing app?

This is a new app or an experiment.

Repro

[Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class SomeController : ControllerBase
    {
        [RequiredScope("myScopeName")]
        public async Task CreateSomething()
        {
              return Task.CompletedTask();
        }
    }

Expected behavior

Request finished with 403 code when pass scope myScopeNameM2M.

Actual behavior

Request finishes with 200 code

Additional context / logs / screenshots / link to code

HttpContext.VerifyUserHasAnyAcceptedScope("myScopeName");

Works as expected.

Anakael avatar Dec 30 '21 16:12 Anakael

@Anakael I cannot repro: image

Can you share what you have in your Startup.cs? Thanks.

jennyf19 avatar Dec 31 '21 17:12 jennyf19

I did nothing special for it except the authentication by jwt token. Did I miss something?

Anakael avatar Dec 31 '21 21:12 Anakael

I would need more information from your Startup.cs. Do you have the same content as when you do dotnet new webapi --auth SingleOrg?

jennyf19 avatar Dec 31 '21 23:12 jennyf19

I've run into this issue as well, repro'd in 2 different projects. What's interesting is it works/doesn't work as follows:

  • works: for access_tokens that are on-behalf-of-the-user via the authorization_code flow the RequiredScope attrib is working as expected
  • doesn't work: for access_tokens via client_credentials flow the RequiredScope attribute does not work (should return Forbidden when scope is missing), but using "HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);" does work

https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-verification-scope-app-roles?tabs=aspnetcore

So for now i'm using the VerifyUserHasAnyAcceptedScope method but would prefer to use the attribute

spacattac avatar Jan 05 '22 16:01 spacattac

Thanks for the explanation, @spacattac. This is by design (and to follow the protocol), but I realize that the design might not be very good, so I'm tempted to start a discussion with the group to simplify things

What happens is:

  • Web APIs called on behalf of users need to verify the Scopes.
  • Web APIs called on behalf of an app (a daemon app) need to verify app roles

But from the point of view of the client app, in both cases you request scopes (you are exposed to app roles when you do the app registration though).

This is all explained in Protected web API: Verify scopes and app roles

Question for you:

  • what behavior would you expect for the API (given the article above)
  • how can we make things more discoverable?

jmprieur avatar Jan 05 '22 17:01 jmprieur

Thanks @jmprieur . i had read that msft docs link and i didn't really understand why the daemon app couldn't/wouldn't use the scope the same way and didn't understand what an app role is/was. in reading more about Azure AD last night i did learn about the app registration and that app role designation is part of that registration. that explains why i didn't/don't really know what it is - i'm not actually using Azure AD and instead am using Okta for OIDC+OAuth and the daemon app registration doesn't have an app role. i can use Access Policies on the Okta auth server to grant the scopes to an app using client_credentials just like i can to an app acting on behalf of the user with authorization_code flow.

spacattac avatar Jan 05 '22 18:01 spacattac

Thanks for the explanation, @spacattac. RequiredScopes just checks at the scp or http://schemas.microsoft.com/identity/claims/scope claims. If Okta generates these, you should be good.

jmprieur avatar Jan 05 '22 18:01 jmprieur

Thanks @jmprieur - yes, okta does generate the scope (scp) claim. i don't understand why the call to HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); works for me but [RequiredScopes(scopeRequiredByApi)] does not. It seems to me there should be parity with these.

spacattac avatar Jan 05 '22 18:01 spacattac

I guess we'd need to see the structure of the token to understand this question? Are you using app.UseAuthorization? Did you customize the Authorization policies?

jmprieur avatar Jan 05 '22 18:01 jmprieur

@jmprieur i'll grab some code and/or screen snippets to share/show ...

spacattac avatar Jan 05 '22 19:01 spacattac

here's a snippet of scope check working as expected via the method but not the attribute.

image

i am using app.UseAuthorization. and in this project i do have 1 policy added but it's not used in the endpoint/method in this example. also, the other project where i've repro'd this same behavior does not have any additional policies.

services.AddAuthorization(options => { options.AddPolicy("AccessPersonPolicy", policy => policy.Requirements.Add(new IsAnAccessPersonRequirement())); }); and here are the User.Claims and you can see the scope claim has "access_token" but not "documents.read".

image

spacattac avatar Jan 05 '22 19:01 spacattac

@spacattac : in the claims, the scope provided by Okta (item 7) has a value "access_token", whereas you want "documents.read". That's the reason.

jmprieur avatar Jan 06 '22 04:01 jmprieur

@jmprieur - not exactly .... what i'm showing you above is i've intentionally left out the documents.create scope and i'm showing you how the [RequiredScopes("documents.create")] attribute did NOT block the call yet the VerifyUserHasAnyAcceptedScope("documents.create") DID block and returns Forbidden. why is there not parity between these two?

spacattac avatar Jan 06 '22 14:01 spacattac

same issue here I have an access_token which I retieve through a Client from AD, then the client calls my WebAPI.

Currently my access_token has a "scp" attribute of "access_api" however when I add an [RequiredScope("access_api_admin")] decorator on a controller action (controller has [Authorize]) and I call the endpoint with the "access_api" scope the action gets executed.

Expected behavior would be an Unauthorized..., yet I am gettin the data which I should not be getting...

The token image

The endpoint with requiredScope "access_api_admin" image

I feel this should block the request I am making with my access_token since the scope "access_api_admin" is not present in the token...

Pieter-1337 avatar Jan 30 '22 12:01 Pieter-1337

RequiredScope used to work as expected in this issue until version 1.17.0 of Microsoft.Identityt.Web. It got broken in 1.18.0. See these issues: https://github.com/AzureAD/microsoft-identity-web/issues/1609 https://github.com/AzureAD/microsoft-identity-web/issues/1002.

In my case adding builder.Services.AddRequiredScopeAuthorization() statement to Program.cs made the RequiredScope attribute work.

gurry avatar Mar 15 '22 06:03 gurry

Closing this one:

  • We released RequiredScopeOrAppPermissionAttribute in Microsoft.Identity.Web 1.25.0
  • @gurry : you are right, it requires to use app.AddAuthorization(). However, AddRequiredScopeAuthorization is called in AddMicrosoftIdentityWebApi. Did you use that method?

jmprieur avatar Aug 21 '22 19:08 jmprieur

@jmprieur Yes, I peeked inside Microsoft's code and saw 'AddRequiredScopeAuthorization() being used there and simply mimicked that. It may well have been AddMicrosoftIdentityWebApi() that I peeked into (don't exactly remember now).

gurry avatar Aug 24 '22 04:08 gurry