[ ] Regression (a behavior that used to work and stopped working in a new release)
[x ] Bug report
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Other... Please describe:
Library version: 1.0.1
## Current behavior
I use msal.js in my vue SPA for authenticating the user with Azure Active Diretory and sending the idToken as authorization header to an API.
Everithing goes well when the user opens the app for the first time, i receive only the id_token:
this.loginRequest = {
scopes: ["openid", "email"],
prompt: 'select_account'
}
this.msalnstance = new Msal.UserAgentApplication(this.config)
this.msalnstance.loginPopup(this.loginRequest)
.then(function (loginResponse) { //here i take the id token from response and the user email }
But after the id token expires and i am trying to renew it, i run into issues. I saw in the acquireTokenSilent method definition that if i send the clientId as scope, then the idToken will be refreshed. First i'll show you the code i use:
this.idTokenRequest = {
scopes: [my_clientId_value],
authority: my_authority_value,
prompt: 'none'
}
this.msalnstance.acquireTokenSilent(this.idTokenRequest)
.then(function (idTokenResponse) {
// here i try to get the user's email again, but i cannot find it in the response
}
This setup brings a value for access token, sometimes is the same as id_token, even if the value for "token_type" in response is "id_token".
Sometimes it brings a different value for access token (with exp date in the future), while id_token is not renewed.
Sometimes the id_token is renewed, but when that happens, it doesn't bring also the "email" value and i cannot add extra scope for that, beside client id value.
Other errors i got when trying to refresh the id_Token are:
- Nonce is not matching, Nonce received: [value], nonce expected : [value]
- set-Cookie header is ignored in response from url https://login.microsoftonline.com/tenentid...... Cookie length should be less than 4096 characters
## Expected behavior
I expect that acquireTokenAsync renew only the id token, not the access token, when i pass clientId in the scopes and also I need a way to bring email in the renewed id token.
Also, because i need only the id token, i shouldn't be constrained to set to "true" in app registration Manifest oauth2AllowImplicitFlow, but only oauth2AllowIdTokenImplicitFlow.
## Minimal reproduction of the problem with instructions
@sameerag / @negoe cc
I think there may be some confusion in usage here. AcquireTokenSilent() is specifically for acquiring an accessToken, not an id token. Id tokens are obtained by logging a user in, or by calling GetAccount() (if already logged in) access token renewal is done.
This is from the acquireTokenSilent method (msal.js 1.01.)definition:
On my local environment the id token is renewed, but i need to add also the "email" scope when i call acquireTokenSilent , so i can take the value from the id token. This is how i identify the logged in user in my API, by sending the id token in the authorization header from SPA app to API and here i validate it and get the email claim value.
the current method to get an id token, with preauthorized scopes is loginPopup or loginRedirect methods. You can get an id token with acquireTokenSilent by passing the clientId in scopes, but then I believe you can not add email to it.
Would something like this work
acquireTokenSilent(requestWithClientId).then(() => {
return acquireTokenSilent(requestWithEmailScope)
}).then(() => {
return acquireTokenSilent(requestWithClientId)
}).then(response => {
// response has idTokenwithClamsYouWant?
});
I think we definitely need to support a simpler way here, but curious if this works.
@madatan ping to try this
Thanks DarylThayl for suggstion. Although I don't think this should be the solution to the issue, I'll try it.
I managed to find a workaround for email by using another field value from id token, but now the biggest issue for me is that sometimes it doesn't renew the id token when calling acquireTokenSilent(requestWithClientId), instead it renews the access token. :(
@madatan I am a little confused on the renewal of access Token comment. We have a check in our code where we renew idToken specifically in the acquireTokenSilent() call if the scope passed is clientId:
if(request.scopes && request.scopes.indexOf(this.clientId) > -1 && request.scopes.length === 1) {
// App uses idToken to send to api endpoints
// Default scope is tracked as clientId to store this token
this.logger.verbose("renewing idToken");
this.renewIdToken(request.scopes, resolve, reject, account, serverAuthenticationRequest);
} else {
// renew access token
this.logger.verbose("renewing accesstoken");
this.renewToken(request.scopes, resolve, reject, account, serverAuthenticationRequest);
}
Can you share your sample code where you get an accessToken renewed instead of idToken?
We also have token renewals documented here, in case if it helps your case.
Hi,
Here is the code:
this.loginRequest = {
scopes: ['openid', 'email'],
prompt: 'select_account',
authority: process.env.VUE_APP_AUTHORITY
}
this.config = {
auth: {
clientId: process.env.VUE_APP_CLIENT_ID,
redirectUri: process.env.VUE_APP_CALLBACK_PATH,
authority: process.env.VUE_APP_AUTHORITY
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: true
}
}
this.msalnstance = new Msal.UserAgentApplication(this.config)
this.renewTokenRequest = {
scopes: [process.env.VUE_APP_CLIENT_ID], // here process.env.VUE_APP_CLIENT_ID is my Azure AD application id value
prompt: 'none',
authority: null, // I tried also without setting authority to null
account: this.msalnstance.getAccount() // this one and the authority value I added because of another thread from git with another id token renewal issue
}
//Method for user login
login() {
let self = this
return new Promise((resolve, reject) => {
let account = this.msalnstance.getAccount(),
hasAccount = !!account
if (hasAccount) {
this.msalnstance.acquireTokenSilent(this.renewTokenRequest)
.then(function (response) {
// here I get the response.idToken.rawIdToken
resolve()
})
.catch(function (error) {
console.error(error)
if (error.errorMessage.indexOf('interaction_required') !== -1 || error.errorMessage.indexOf('no user is signed in') !== -1 ) {
self.interactiveLogin()
.then((email) => {
console.log('interactive login email', email)
resolve(email)
})
.catch((error) => reject(error))
} else {
reject(error)
}
})
} else {
this.interactiveLogin()
.then((email) => {
resolve(email)
})
.catch((error) => {
console.log(error)
reject(error)
})
}
})
}
//Method used for checking if user is logged in and also for id token renewal
userIsLoggedIn() {
let self = this
return new Promise((resolve, reject) => {
let account = this.msalnstance.getAccount()
let hasAccount = !!account
if (hasAccount) {
this.msalnstance.acquireTokenSilent(this.renewTokenRequest)
.then(function (response) {
//// here I get the response.idToken.rawIdToken
resolve()
})
.catch((error) => {
console.log('Error in userIsLoggesIn method')
if (error.errorMessage.indexOf('interaction_required') !== -1 || error.errorMessage.indexOf('no user is signed in') !== -1) {
self.interactiveLogin()
.then((email) => {
resolve(true)
})
.catch((error) => reject(error))
} else {
reject(error)
}
console.log(error)
})
} else {
resolve(false)
}
})
}
Hi,
I'm also curios how can we renew the id_token with email claim in it?
@madatan have figured this out?
Is there a status update for this issue? We are actively waiting on the solution.
@sameerag do you have anything you can add here on this issue?
Does the claims object you added here add this?
This problem should be fixed in recent releases, please upgrade to [email protected] and test again. Comment if this is still a problem.
@jmckennon I updated to [email protected] and still have the issue, is there a new flag like forceRefresh that I need to use to force an Id_token renewal?
forceRefresh flag still only refreshes the access token when used in acquireTokenSilent.
@arian2ashk Can you please elaborate on the issue you are seeing, and how you are using the library?
@jasonnutter the setup we have is an API and a react app that talks to the API. the API manages users and roles and permissions and it uses Azure AD for providing the identity. we have different ways of authenticating in our API for different customers. if there is a token in the header meaning the customer has a token provider, it will validate the token and then use the info in the token to retrieve the user from the database.
When using AzureAD since token issued for Microsoft Graph cannot be validated we use the Id_token to validate the token and we use the access token to retrieve some user's info from Graph on the API side. because of this we need both access token and id token. on the react side we have a provider that will authenticate and provide a token on the context, so underlying components can pick up the token and attach it to the header of the request they make. when a user has been authenticated and tokens exist a token renewal task will be scheduled that will renew both access token and Id token before the expiration and change the tokens in the context. here is the simplified code that does the authentication and renewal:
login = (onAuthenticationSuccess, onAuthenticationFail, authParams = {scopes: ['openid', 'email'], forceRefresh: true}) => {
this.myMSALObj
.acquireTokenSilent(authParams)
.then(authResult => {
if (authResult && authResult.accessToken && authResult.idToken) {
/** schedule a token renewal based on when the current token will expire */
this.scheduleRenewal(authResult.idToken.expiration);
onAuthenticationSuccess(authResult.idToken.rawIdToken, authResult.accessToken);
}
})
.catch(e => {
try {
/* No session exists, User needs to login. Calls loginRedirect to redirect to AzureAD login page */
this.myMSALObj.loginRedirect(authParams);
} catch (error) {
onAuthenticationFail();
}
});
};
scheduleRenewal = (expiration, onAuthenticationSuccess, onAuthenticationFail) => {
var expiresIn = expiration * 1000 - new Date().getTime();
//renew the token when 90% of the time has passed to make sure token is renewed before expiring
var delay = expiresIn * 0.9;
if (delay > 0) {
this.tokenRenewalTimeout = setTimeout(
that => that.login(onAuthenticationSuccess, onAuthenticationFail, {scopes: ['openid', 'email'], forceRefresh: true}),
delay,
this
);
}
};
We need both tokens to be renewed and the forceRefresh flag only renews the access token. I know our solution may not be the best solution or following the standard way but it worked for our other token providers, and since our solution needs to be generic enough to work for all our customers with different token providers, we needed to diverge from the standard way. Thanks in advance for helping.
@arian2ashk Thanks for the info. A couple of thoughts:
acquireTokenSilent right before you need an access token, and rely on MSAL to either return you a cached token, renew your token, or throw an error prompting you to call one of the interactive methods.Here are the steps I would implement:
loginRedirect or loginPopup.acquireTokenSilent. acquireTokenPopup or acquireTokenRedirect. One reason this could happen would be if there is not a valid token in the cache and the user no longer has an active AAD session. Calling one of the interactive methods will give you a new access token and new ID token, in that case.Let me know if that makes sense, and if you have further questions.
@jasonnutter Thanks for the feedback, It helped a lot, I managed to only use the access token for the authentication by using custom scopes. The only question I have right now is that why does MSAL loginRedirect and acquireTokenSilent require implicit grant to ID tokens. I was expecting now that I am not using the ID token I should be able to remove the implicit grant to ID tokens.
@arian2ashk You're welcome!
MSAL requires an ID token because it requires context about the user, which the ID token provides.
Most helpful comment
@arian2ashk Thanks for the info. A couple of thoughts:
acquireTokenSilentright before you need an access token, and rely on MSAL to either return you a cached token, renew your token, or throw an error prompting you to call one of the interactive methods.Here are the steps I would implement:
loginRedirectorloginPopup.acquireTokenSilent.acquireTokenPopuporacquireTokenRedirect. One reason this could happen would be if there is not a valid token in the cache and the user no longer has an active AAD session. Calling one of the interactive methods will give you a new access token and new ID token, in that case.Let me know if that makes sense, and if you have further questions.