Search code examples
oauth-2.0azure-active-directoryopenidazure-ad-msalmsal.js

Tokens returned by MSAL.JS contain none of the optional claims


The setup is as follows:

  • SPA web app that connects to a backend API. It's supposed to log in the user, request an access_token to the API and use it on all requests. Call it the Application.
  • Backend API that is supposed to receive access_tokens and authorise users based on the groups claim contained in it. Call it the API.

Configuration on the side of Azure AD:

  • The Application has redirect uris configured and is given an API permission to the API (the API.User scope).
  • The Backend exposes the API.User scope and authorises The Application as an Authorized client app.

Now, to get the groups claim I've added it to token configuration in both apps. Since it didn't work, I went and added some more optional claims to see if any of them are returned. They're not. The relevant parts of the manifest look like this:

"groupMembershipClaims": "All",
"oauth2AllowIdTokenImplicitFlow": true,
"oauth2AllowImplicitFlow": true,
"optionalClaims": {
        "idToken": [
            {
                "name": "email",
                "source": null,
                "essential": false,
                "additionalProperties": []
            },
            {
                "name": "groups",
                "source": null,
                "essential": false,
                "additionalProperties": []
            },
            {
                "name": "nickname",
                "source": null,
                "essential": false,
                "additionalProperties": []
            },
            {
                "name": "given_name",
                "source": null,
                "essential": false,
                "additionalProperties": []
            }
        ],
        "accessToken": [
            {
                "name": "email",
                "source": null,
                "essential": false,
                "additionalProperties": []
            },
            {
                "name": "groups",
                "source": null,
                "essential": false,
                "additionalProperties": []
            },
            {
                "name": "nickname",
                "source": null,
                "essential": false,
                "additionalProperties": []
            },
            {
                "name": "given_name",
                "source": null,
                "essential": false,
                "additionalProperties": []
            }
        ],
        "saml2Token": [
            {
                "name": "groups",
                "source": null,
                "essential": false,
                "additionalProperties": []
            }
        ]
}

Both app registrations are configured the same (with respect to the above values).

The frontend code retrieving the token:

    msalInstance = new Msal.UserAgentApplication(
        {
          auth: {
            clientId: <the Application's client id>
          }
        }
    );

    this.msalInstance.handleRedirectCallback((error, response) => {
         console.warn("In handleRedirectCallback");
         console.warn(JSON.stringify(error));
         console.warn(JSON.stringify(response));
      });

      if (this.msalInstance.getAccount()) {
        const request = {
          scopes: ['api://<the API client id>/API.User']
        }
        this.msalInstance.acquireTokenSilent(request)
          .then(response => {
            console.warn("In acquireTokenSilent then");
            console.warn(JSON.stringify(response));
          })
          .catch(err => {
            if (err.name === 'InteractionRequiredAuthError') {
              return this.msalInstance.acquireTokenRedirect(request);
            }
          });
      }
      else {
        const request = {
          scopes: ['user.read', 'email', 'openid', 'profile']
        };

        this.msalInstance.loginRedirect(request);
      }

I am clearly doing something wrong, as all access_tokens and id_tokens have the same layout completely independent of any changes I make to the app registrations or the request.scopes. I see that the MSAL request has a claimsRequest field, but it's undocumented and I don't know what should be put there. The MSDN docs make it sound as if all I needed to do was configure the app registration and the claims would be appended to the retrieved tokens, but it's clearly not the case.

Is there something else I have to configure to get the groups claim?

EDIT:

Example id_token layout that I'm getting:

{
  "aud": "api://<the Application client id>",
  "iss": "https://login.microsoftonline.com/<the Tenant id>/",
  "iat": 1585052362,
  "nbf": 1585052362,
  "exp": 1585056262,
  "aio": <Azure AD opaque string>,
  "given_name": "<My first name>",
  "hasgroups": "true",
  "name": "<My account name>",
  "nonce": <some guid>,
  "oid": "<my account OID>",
  "preferred_username": "<my email>",
  "sub": <...>,
  "tid": "<the tenant id>",
  "uti": <Azure AD opaque string>,
  "ver": "2.0"
}

example access_token I'm getting:

{
  "aud": "api://<the Application client id>",
  "iss": "https://sts.windows.net/<the Tenant id>/",
  "iat": 1585052365,
  "nbf": 1585052365,
  "exp": 1585056265,
  "acr": "1",
  "aio": <Azure AD opaque string>,
  "appid": "<the Application client id>",
  "appidacr": "0",
  "family_name": <my last name>,
  "given_name": <my first name>,
  "hasgroups": "true",
  "ipaddr": <my ip addr>,
  "name": <my account name>,
  "oid": <my account oid>,
  "onprem_sid": <not sure what this is, but some identifier>,
  "scp": "API.User",
  "sub": <...>,
  "tid": <the tenant ID>,
  "unique_name": <my email>,
  "upn": <my email>,
  "uti": <Azure AD opaque string>,
  "ver": "1.0"
}

Solution

  • Since you commented that you are getting the hasgroups claim, it means the groups were not included in the token for most likely, size restrictions. The claim is documented here: https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens.

    Relevant quote from there:

    If present, always true, denoting the user is in at least one group. Used in place of the groups claim for JWTs in implicit grant flows if the full groups claim would extend the URI fragment beyond the URL length limits (currently 6 or more groups)

    So 5 is the maximum in implicit flow. The 200 maximum is for e.g. authorization code flow.