I am upgrading an app using msal.js v1.3 to v2.3 and I'm having a problem retreiving the access token once I get my id token.
I initialize the handleRedirectPromise in my constructor. Then, when the user clicks the login button, I call loginRedirect and pass in an object that has the openid scope and the scope from my separately registered api. This works well, the id token comes back and I call acquireTokenSilent to retreive my access token. I pass an object that has my registered api's scope and account from the loginRedirect call into this function.
The problem is that the authorization response from the acquireTokenSilent has an empty access token. The result from the token endpoint looks like:
client_info: "xx"
id_token: "xx"
not_before: 1602895189
refresh_token: "xx"
refresh_token_expires_in: 1209600
scope: ""
token_type: "Bearer"
It doesn't have an access token, but it does specifiy the token type as Bearer
There is no access token in the response and it looks like the scopes property returning is empty. Here is my code:
private msalConfig: Msal.Configuration = {
auth: {
clientId: environment.clientID,
authority: 'https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/B2C_1_DefaultSignInSignUp',
knownAuthorities: ['<tenant>.b2clogin.com'],
navigateToLoginRequestUrl: true,
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: false,
}
};
private loginRequest: Msal.RedirectRequest = {
scopes: ['openid' , 'offline_access', 'https://<tenant>.onmicrosoft.com/api/read' ]
};
private accessTokenRequest: Msal.SilentRequest = {
scopes: ['https://<tenant>.onmicrosoft.com/api/read'] ,
account: null
};
constructor() {
const _this = this;
this.msalInstance = new Msal.PublicClientApplication(this.msalConfig);
this.aquireSilent = (request: Msal.SilentRequest): Promise<Msal.AuthenticationResult> => {
return _this.msalInstance.acquireTokenSilent(request).then(
access_token => {
_this.cacheExpiration(access_token.expiresOn);
_this.isLoggedIn$.next(true);
return access_token;
},
function (reason) {
console.error(reason);
},
);
};
this.msalInstance
.handleRedirectPromise()
.then((tokenResponse: Msal.AuthenticationResult) => {
if (tokenResponse !== null) {
const id_token = tokenResponse.idToken;
const currentAccounts = this.msalInstance.getAllAccounts()
this.accessTokenRequest.account = currentAccounts[0];
this.aquireSilent(this.accessTokenRequest)
}
})
.catch(error => {
console.error(error);
});
}
public login() {
this.msalInstance.loginRedirect(this.loginRequest);
}
Why is the access token not coming back from the token endpoint? Does it have to do with the scopes returning empty? I tried removing the scopes and putting in invalid entries and an error gets raised so I know my request going out is at least valid. Also, just to verify, I have 2 app registrations in AAD, one I created for my spa that has code flow and my older registration I have for my api with an exposed api and scope.
acquireTokenSilent
will return an access token only if there is already an entry for that token in the cache. So if for some reason the token was never obtained previously (vialoginRedirect
, for instance), it will not be able to acquire it silently.That seems to be the issue in your case. You are mixing scopes for different resources in your
loginRequest
, and that's perhaps causing the issue in the new version of the library (access tokens are issued per-resource-per-scope(s). See this doc for more) Try modifying yourloginRequest
object like this:Also, the recommended pattern of usage with
acquireTokenSilent
is that you should fall back to an interactive method (e.g.acquireTokenRedirect
) if theacquireTokenSilent
fails for some reason.So I would modify it as:
A similar issue is discussed here