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

acquireTokenSilent - refreshed ID tokens have incomplete scope #1779

Closed
dluc opened this issue Jun 14, 2020 · 9 comments · Fixed by #1628, #2022 or #2158
Closed

acquireTokenSilent - refreshed ID tokens have incomplete scope #1779

dluc opened this issue Jun 14, 2020 · 9 comments · Fixed by #1628, #2022 or #2158
Assignees
Labels
question Customer is asking for a clarification, use case or information.

Comments

@dluc
Copy link
Contributor

dluc commented Jun 14, 2020

My app uses 4 scopes [profile, email, openid, User.Read] when retrieving an ID token and everything works fine, e.g. the initial ID token includes the "email" claim.

When the ID token expires, the app uses UserAgentApplication.acquireTokenSilent to fetch a new token, but ID token received is missing the email claim and other info related to User.Read scope. As stated in the code "To renew idToken, please pass clientId as the only scope in the Authentication Parameters", the app is passing only the clientId in the scopes array.

The library is configured to use session storage, but I've experienced the same issue with localstorage.

After forking the repo and modifying a file (see below), I managed to fix this problem, so it appears to be an issue in the library. I'm looking for suggestions so that I don't have to maintain a fork of the lib.

Looking at the http requests, this is the first request, which uses the 4 scopes configured in my app, and it gets and ID token with all the claims as expected:

https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize
  ?response_type=token
  &scope=profile%20email%20openid%20User.Read
  &...

This is the request used by MSAL to refresh the ID token, via acquireTokenSilent, and it has only 2 scopes (openid and profile):

https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize
  ?response_type=id_token
  &scope=openid%20profile
  &...

UserAgentApplication.acquireTokenSilent accepts a userRequest parameter, which has scopes and extraScopesToConsent parameters, but no matter how these params are set, custom scopes are ignored, and the request to login.microsoftonline.com includes only 2 scopes (openid and profile), which are hardcoded in UrlUtils.translateclientIdUsedInScope

private static translateclientIdUsedInScope(scopes: Array<string>, clientId: string): void {

RequestUtils.validateRequest is also designed to ignore extraScopesToConsent in this scenario, and the library causes an infinite loop if scopes contains anything other that the client id.

As mentioned, this is not a problem with AAD. After modifying UrlUtils.translateclientIdUsedInScope to include my custom scopes, I managed to get the refreshed ID tokens with all the information I needed, i.e. the same claims of the initial ID token.

private static translateclientIdUsedInScope(scopes: Array<string>, clientId: string): void {    
    const clientIdIndex: number = scopes.indexOf(clientId);
    if (clientIdIndex >= 0) {
        scopes.splice(clientIdIndex, 1);
        if (scopes.indexOf("openid") === -1) {
            scopes.push("openid");
        }
        if (scopes.indexOf("profile") === -1) {
            scopes.push("profile");
        }

        // Workaround for missing claims in refreshed ID tokens
        if (scopes.indexOf("email") === -1) {
            scopes.push("email");
        }
        
        // Workaround for missing claims in refreshed ID tokens
        if (scopes.indexOf("User.Read") === -1) {
            scopes.push("User.Read");
        }
    }
}

Call stack when renewing ID tokens, leading to 2 scopes only, regardless of the initial params

  1. UserAgentApplication.acquireTokenSilent (scope contains only clientId as documented)
  2. -> UserAgentApplication.renewIdToken (this is called only if scope contains clientId)
  3. -> UrlUtils.createNavigateUrl (this is called passing serverAuthenticationRequest which contains a copy of the scopes in request)
  4. -> UrlUtils.createNavigationUrlString
  5. -> UrlUtils.translateclientIdUsedInScope
@dluc dluc added the question Customer is asking for a clarification, use case or information. label Jun 14, 2020
@tnorling
Copy link
Collaborator

@dluc ID Tokens do not have scopes associated with them. It sounds like you are looking for an access token.

The reason your User.Read and email scopes are requested in your first call is because you requested an access token, then in the 2nd you are requesting an id_token which requests only openid and profile by design. Notice the response_type in both network calls you shared above.

So to summarize there's two things to be aware of here:

  1. If you are looking to renew an id_token, pass clientId as you did
  2. If you are looking to renew the access token which provides you access to User.Read and email, you should pass User.Read and email

Hope this clears things up a bit. Let me know if you have any follow up questions!

@tnorling tnorling self-assigned this Jun 14, 2020
@dluc
Copy link
Contributor Author

dluc commented Jun 16, 2020

hi @tnorling I'm looking at ID tokens (not access token), which has scopes, e.g. see the 2 HTTP requests above.

The first has 4 scopes: scope=profile%20email%20openid%20User.Read
The second has 2 scopes: scope=openid%20profile

The scope regulates which claims are included in the JWT token. The token received from the first HTTP request for example, includes the user email and the full name. The second requests, which is missing 2 out of 4 scopes, is missing that info, hence my question.

More importantly, if you attempt the second request adding all the 4 scopes, e.g. to refresh an ID token, you will get a new ID token with all the claims like the original one.

As I mentioned, it's a limitation of the code, because the service allows to do that, but I rather not have to maintain a fork to MSAL.

@dluc
Copy link
Contributor Author

dluc commented Jun 16, 2020

summarizing, when refreshing the ID token, we would like that this request

https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize
  ?response_type=id_token
  &scope=openid%20profile
  &...

included all the scopes configured, e.g. be

https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize
  ?response_type=id_token
  &scope=profile%20email%20openid%20User.Read
  &...

so that the ID token released by AAD had the extra user information (email address and full name)

@tnorling
Copy link
Collaborator

@dluc Gotcha. To my knowledge, this is not a scenario we have considered before. We are currently working on updating the token response_type logic for acquireToken calls which I believe will enable this functionality for you. With those changes you will receive a renewed id_token in addition to an access_token anytime openid, profile and/or clientid are present in the scopes requested. See #1628 and #1681

Until those PRs are merged and we put out a new release there are 2 things you could try:

  1. Call one of the login methods (loginRedirect, loginPopup, ssoSilent). These methods should always return an id_token and should not sanitize the scopes input
  2. Add the claims you need your id_tokens to contain to your app registration in the azure portal. This will ensure your id_tokens always contain what you need regardless of what you pass as scopes.

@dluc
Copy link
Contributor Author

dluc commented Jun 17, 2020

thanks @tnorling, will try that out! looking forward to see the updates merged :-)

@tnorling tnorling linked a pull request Jun 17, 2020 that will close this issue
@github-actions
Copy link
Contributor

github-actions bot commented Jul 2, 2020

This issue has not seen activity in 14 days. It may be closed if it remains stale.

@github-actions github-actions bot added the no-issue-activity Issue author has not responded in 5 days label Jul 2, 2020
@scotteby
Copy link

Hello,

We have this same requirement where we need to refresh the ID token and also get the email claim included.

Glad to see you already have this fixed and it looks like it will be available in version 1.4.

Any idea when that might get released?

@github-actions github-actions bot removed the no-issue-activity Issue author has not responded in 5 days label Jul 31, 2020
@tnorling
Copy link
Collaborator

tnorling commented Aug 3, 2020

@scotteby We don't have a specific date yet, we're currently working on putting out 1.3.4 and we're hoping to get 1.4.0 out shortly thereafter. I will update here when it is available.

@github-actions
Copy link
Contributor

This issue has not seen activity in 14 days. It may be closed if it remains stale.

@github-actions github-actions bot added the no-issue-activity Issue author has not responded in 5 days label Aug 18, 2020
@jasonnutter jasonnutter linked a pull request Aug 24, 2020 that will close this issue
@jasonnutter jasonnutter removed the no-issue-activity Issue author has not responded in 5 days label Aug 24, 2020
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 1, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.