AzureAD / microsoft-authentication-library-for-js

Microsoft Authentication Library (MSAL) for JS
http://aka.ms/aadv2
MIT License
3.64k stars 2.65k forks source link

acquireTokenSilent invalid graph API access token after fetching a token for your own API. clientID aud & scope is retained in the 2nd acquireTokenSilent call you make with [User.Read] scope #3276

Closed OwenWheatley closed 3 years ago

OwenWheatley commented 3 years ago

Library

Framework

Description

So I have a react application where I use acquireTokenSilent to get an access token for my own API passing the scope of [myclientID/default] which is configured in the AD portal and then I want to make a separate call of acquireTokenSilent with the scope of [User.Read] to give me an access token to use with the MS graph API.

If I get the Graph API access token first then it's valid and works fine with Graph API. However if I get an access token for my own API then try to get a Graph API access token with a separate acquireTokenSilent call (example in reproduction steps) then the token I get back is invalid (missing the graph api aud of 00000003-0000-0000-c000-000000000000 and instead having my own clientID as the aud along with the format almost matching the [myclientID/default] scope instead of the [User.Read] scope.

This means I can successfully fetch a graph API token, but then fetch a token for my own API and upon trying to fetch another graph API token it isn't valid...

Reproduction steps

Here is an example of the two SEPERATE .acquireTokenSilent calls that I make. The top one for the graph API & the bottom for my own API. image

Calling generateGraphAPIToken I can see the following scope with the token that I console log out. image This token works fine with the graph API.

Then calling generateMyOwnAPIToken I can see the following scope with the token that I console log out. New Project (5)

If I now went and called generateGraphAPIToken after having fetched a token with my own API's scope it's almost as if it's caching the clientID and using it within this calls scope, it just doesn't give me a graph API token (wrong aud, missing nonce in header etc..). I'll also point out I'm not trying to have one token that multiple scopes, these are separate acquireTokenSilent calls with the properly defined scopes as per the docs. (Perhaps I've miss-read them though?) New Project (4)

Expected behavior

Being able to use acquireTokenSilent to fetch a Graph API token AND a separate resource API token (for my own API).

Identity Provider

Browsers/Environment

github-actions[bot] commented 3 years ago

Invalid Issue Template: Please update the original issue and make sure to fill out the entire issue template so we can better assist you.

tnorling commented 3 years ago

@OwenWheatley Do you get this same behavior if you define a custom scope on your app registration instead of using the /.default scope?

OwenWheatley commented 3 years ago

Thanks for such a speedy response, appreciate it!

Yes the behaviour is the same when changing to use a custom scope. I did find that I made an error in forgetting to prepend the api:// to my clientID for the scope (oops my bad, it was late!).

However even with amending this what I'm seeing is that my first request for a graph API token will first generate a correct token. But then when I generate a token for another API the scope shows fine for this new token eg. api://myclientID/example.read without the user.read scope in there etc.. But then what I find is when trying to generate a graph API token after that point it just generates another API token with the api://myclientID/example.read scope instead of the User.Read scope.

I will break it down in an example below.

First acquireTokenSilent Request - Graph API Token Request

Javascript code within my react application that calls acquireTokenSilent for the graph API with the [User.Read] scope. image

Console logging this acquireTokenSilent response (When viewing the access token in jwt.io I can see the aud is 00000003-0000-0000-c000-000000000000 & has a nonce in the header like graph API wants. It's a valid graph API token that I can use) Graph API First Call Console Log Response 01

Second acquireTokenSilent Request - My Own API (scope defined in app registration) Token Request

Javascript code within my react application that calls acquireTokenSilent for the graph API with the [api://myClientID/example.read] scope. image

Console logging this acquireTokenSilent response (When viewing this access token I can see the aud is my client ID and my own API takes this token fine) My API First Call Console Log Response 02

Third acquireTokenSilent Request - Graph API Token Request (Using same method as in the first call)

Console logging this acquireTokenSilent response Graph API SecondCall Console Log Response 03

As you can see where I'm expecting a graph API token from the 2nd time I call the acquireTokenSilent with scope of [User.Read] I actually get what I'd expect from the custom scope I pass?

If there is any additional information I can provide that would be useful please let me know! Apologies if I blotted some pertinent information.

tnorling commented 3 years ago

@OwenWheatley It looks like you're going to the network for each of these, can you send me a fiddler trace to the email on my profile? I'm also curious why you're getting a cache miss here but let's tackle that once we determine why the server is sending you back an unexpected token.

OwenWheatley commented 3 years ago

@tnorling Thanks, I've sent you an email :)

tnorling commented 3 years ago

@OwenWheatley The network logs show the graph token requests as only requesting scopes openid and profile, not User.Read. This is likely the problem. I'm noticing in your graph API call code you have the param scope instead of scopes which is why User.Read is dropped from the request. Please fix that and give it another try.

OwenWheatley commented 3 years ago

Ahh I'm such a fool, how could I have missed that! I have no idea why I wrote scope instead of scopes just for the graph API and how I didn't even see the missing 's'!. I'm confused as to why it gave me a token at all then :S more so why it worked with graph API to!

What's weird though is that tokenResponse.account returns as null when I use scopes yet returns with my username when I use scope :S

Other than that though it works perfectly now. I can get a Graph API token & my own API token with no issue!

Thanks so much for your help @tnorling sorry for the non-issue!

tnorling commented 3 years ago

Ahh I'm such a fool, how could I have missed that! I have no idea why I wrote scope instead of scopes just for the graph API and how I didn't even see the missing 's'!. I'm confused as to why it gave me a token at all then :S more so why it worked with graph API to!

AAD interprets OIDC scopes as graph scopes if they come in on their own (I agree this is confusing and controversial, but that's a separate discussion). I think the reason it didn't work the 2nd time is that, with the absence of actual resource scopes, AAD exchanged the refresh token for an access token with the scopes that were used when the refresh token was obtained (i.e. your API scope)

What's weird though is that tokenResponse.account returns as null when I use scopes yet returns with my username when I use scope :S

This is odd and unexpected. If you can't figure out what's going on here feel free to open a new issue and we can take a look to see why this is happening.