Warning - Account used to refresh tokens not in persistence, refreshed tokens will not be stored in the cache
See original GitHub issueCore Library
MSAL.js v2 (@azure/msal-browser)
Core Library Version
2.26.0
Wrapper Library
MSAL React (@azure/msal-react)
Wrapper Library Version
1.4.2
Description
I am writing an SPA with back-end REST API. After updating my npm packages, I noticed that my browser’s console log is full of warnings when running my app saying
@azure/msal-common@7.0.0 : Warning - Account used to refresh tokens not in persistence, refreshed tokens will not be stored in the cache
I’ve also noticed that requests to the endpoint
https://mycompanyauth.b2clogin.com/mycompanyauth.onmicrosoft.com/b2c_1a_signup_signin/oauth2/v2.0/token
take between 3 and 11 seconds which seems rather long to me.
What’s wrong here?
Error Message
@azure/msal-common@7.0.0 : Warning - Account used to refresh tokens not in persistence, refreshed tokens will not be stored in the cache
Msal Logs
No response
MSAL Configuration
const ua = window.navigator.userAgent
const msie = ua.indexOf("MSIE ")
const msie11 = ua.indexOf("Trident/")
const msedge = ua.indexOf("Edge/")
const firefox = ua.indexOf("Firefox")
const isIE = msie > 0 || msie11 > 0 // eslint-disable-line @typescript-eslint/no-unused-vars
const isEdge = msedge > 0
// Only needed if you need to support the redirect flow in Firefox incognito:
const isFirefox = firefox > 0 // eslint-disable-line @typescript-eslint/no-unused-vars
export const msalConfig: Configuration = {
auth: {
clientId: process.env.REACT_APP_CLIENT_ID as string,
authority: b2cConfiguration.policies.signUpSignIn.authority,
knownAuthorities: [b2cConfiguration.authorityDomain],
redirectUri: process.env.REACT_APP_REDIRECT_URI as string,
navigateToLoginRequestUrl: true,
postLogoutRedirectUri: process.env.REACT_APP_POST_LOGOUT_REDIRECT_URI as string
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: isEdge // Set this to "true" if you are having issues on Edge
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) {
return
}
switch (level) {
case LogLevel.Error:
console.error(message)
return
case LogLevel.Info:
console.debug(message)
return
case LogLevel.Verbose:
console.debug(message)
return
case LogLevel.Warning:
console.warn(message)
return
}
}
}
}
}
Relevant Code Snippets
File azure-auth-config.ts
:
import { Configuration, LogLevel, RedirectRequest } from "@azure/msal-browser"
// Browser check variables
// If you support IE, our recommendation is that you sign-in using Redirect APIs
// If you as a developer are testing using Edge InPrivate mode, please add "isEdge" to the if check
const ua = window.navigator.userAgent
const msie = ua.indexOf("MSIE ")
const msie11 = ua.indexOf("Trident/")
const msedge = ua.indexOf("Edge/")
const firefox = ua.indexOf("Firefox")
const isIE = msie > 0 || msie11 > 0 // eslint-disable-line @typescript-eslint/no-unused-vars
const isEdge = msedge > 0
// Only needed if you need to support the redirect flow in Firefox incognito:
const isFirefox = firefox > 0 // eslint-disable-line @typescript-eslint/no-unused-vars
export type B2CConfiguration = Readonly<{
authorityDomain: string,
policies: Readonly<{
signUpSignIn: Readonly<{
name: string
authority: string
}>
resetPassword: Readonly<{
name: string
authority: string
}>
editProfile: Readonly<{
name: string
authority: string
}>
}>
}>
/**
* Enter here the user flows and custom policies for your B2C application
* To learn more about user flows, visit: https://docs.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview
* To learn more about custom policies, visit: https://docs.microsoft.com/en-us/azure/active-directory-b2c/custom-policy-overview
*/
export const b2cConfiguration: B2CConfiguration = {
authorityDomain: process.env.REACT_APP_AUTHORITY_DOMAIN as string,
policies: {
signUpSignIn: {
name: process.env.REACT_APP_POLICY_NAME_SIGNUPSIGNIN as string,
authority: process.env.REACT_APP_AUTHORITY_SIGNUPSIGNIN as string
},
resetPassword: {
name: process.env.REACT_APP_POLICY_NAME_RESETPASSWORD as string,
authority: process.env.REACT_APP_AUTHORITY_RESETPASSWORD as string
},
editProfile: {
name: process.env.REACT_APP_POLICY_NAME_EDITPROFILE as string,
authority: process.env.REACT_APP_AUTHORITY_EDITPROFILE as string
},
}
}
/**
* Configuration object to be passed to MSAL instance on creation.
* For a full list of MSAL.js configuration parameters, visit:
* https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md
*/
export const msalConfig: Configuration = {
auth: {
clientId: process.env.REACT_APP_CLIENT_ID as string,
authority: b2cConfiguration.policies.signUpSignIn.authority,
knownAuthorities: [b2cConfiguration.authorityDomain],
redirectUri: process.env.REACT_APP_REDIRECT_URI as string,
navigateToLoginRequestUrl: true,
postLogoutRedirectUri: process.env.REACT_APP_POST_LOGOUT_REDIRECT_URI as string
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: isEdge // Set this to "true" if you are having issues on Edge
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) {
return
}
switch (level) {
case LogLevel.Error:
console.error(message)
return
case LogLevel.Info:
console.debug(message)
return
case LogLevel.Verbose:
console.debug(message)
return
case LogLevel.Warning:
console.warn(message)
return
}
}
}
}
}
/**
* Scopes you add here will be prompted for user consent during sign-in.
* By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request.
* For more information about OIDC scopes, visit:
* https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
*/
export const loginRequest: RedirectRequest = {
scopes: [
],
authority: b2cConfiguration.policies.signUpSignIn.authority
}
export const resetPasswordRequest: RedirectRequest = {
scopes: [
],
authority: b2cConfiguration.policies.resetPassword.authority
}
export const editProfileRequest: RedirectRequest = {
scopes: [
],
authority: b2cConfiguration.policies.editProfile.authority
}
export const backEndConfig = {
// see https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md
accessTokenScopes: [
`${process.env.REACT_APP_BACK_END_APP_ID_URI}/Api.AccessAsUser`
]
}
File azure-auth-provider.ts
:
import { AccountInfo, AuthError, EventMessage, EventType, InteractionRequiredAuthError, IPublicClientApplication, PublicClientApplication } from "@azure/msal-browser"
import { b2cConfiguration, msalConfig, resetPasswordRequest } from "./azure-auth-config"
export const msalSingleton: IPublicClientApplication = new PublicClientApplication(msalConfig)
// Account selection logic is app dependent. Adjust as needed for different use cases.
const accounts = msalSingleton.getAllAccounts()
if (accounts.length > 0) {
msalSingleton.setActiveAccount(accounts[0])
}
// Compare https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/2084#issuecomment-675013771
type IdTokenClaims = {
tfp: string
}
msalSingleton.addEventCallback((event: EventMessage) => {
if (event.eventType === EventType.LOGIN_FAILURE) {
/**
* Gets triggered when user clicks on "forgot password" button in Microsoft Login Page.
* Microsoft only redirects to application, but does NOT execute any userflow. This has
* to be done by the application.
* See https://github.com/Azure-Samples/active-directory-b2c-javascript-msal-singlepageapp/issues/9
* and https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/1245
*/
if (event.error && event.error instanceof AuthError && event.error.errorMessage.indexOf("AADB2C90118") > -1) {
console.debug("Invoking B2C password reset policy...")
msalSingleton.loginRedirect(resetPasswordRequest)
}
}
if (event.eventType === EventType.LOGIN_SUCCESS || event.eventType === EventType.ACQUIRE_TOKEN_SUCCESS){
if(event.payload){
/**
* We need to reject id tokens that were not issued with the default sign-in policy.
* The "tfp" claim in the token tells us what policy is used.
* To learn more about B2C tokens, visit https://docs.microsoft.com/en-us/azure/active-directory-b2c/tokens-overview
*/
if("idTokenClaims" in event.payload && event.payload.idTokenClaims){
const idTokenClaims = event.payload.idTokenClaims as IdTokenClaims
if (idTokenClaims.tfp === b2cConfiguration.policies.resetPassword.name) {
msalSingleton.logoutRedirect();
} else if (idTokenClaims.tfp === b2cConfiguration.policies.editProfile.name) {
msalSingleton.logoutRedirect()
}
}
if("account" in event.payload && event.payload.account){
const account: AccountInfo = event.payload.account
console.debug("Login successful. Setting account information...")
msalSingleton.setActiveAccount(account)
console.debug("Account information set.")
}
}
}
})
export async function acquireSilentAccessTokenFromAzure(msalInstance: IPublicClientApplication, scopes: string[]): Promise<string>{
const account = msalInstance.getActiveAccount()
if (!account) {
throw new Error("No active account! Verify a user has been signed in and msalInstance.setActiveAccount has been called.")
}
const request = {
scopes,
account: account
}
try{
const accessToken = (await msalInstance.acquireTokenSilent(request)).accessToken
console.debug("Access Token acquired:")
console.debug(accessToken)
return accessToken
} catch (error){
if (error instanceof InteractionRequiredAuthError) {
// fallback to interaction when silent call fails:
await msalInstance.acquireTokenRedirect(request)
throw new Error("The acquireTokenRedirect method did not trigger a redirect. This should never happen!")
} else {
console.error(error)
throw error
}
}
}
File hooks.ts
:
export function useAcquireSilentAccessTokenFromAzure(scopes: string[]): () => Promise<string> {
const { instance: msalInstance } = useMsal()
const acquire = useCallback(() => acquireSilentAccessTokenFromAzure(msalInstance, scopes), [msalInstance, scopes])
return acquire
}
export function useAcquireBackEndAccessToken(): () => Promise<string>{
return useAcquireSilentAccessTokenFromAzure(backEndConfig.accessTokenScopes)
}
Now in a React component get the hook
const acquireBackEndAccessToken = useAcquireBackEndAccessToken()
and request a token:
const token = await acquireBackEndAccessToken()
Check the console for warnings.
Reproduction Steps
Happens each time an access token is requested.
Expected Behavior
Empty console log and snappy token requests with response times under 1 second.
Identity Provider
Azure B2C Custom Policy
Browsers Affected (Select all that apply)
Chrome
Regression
No response
Source
External (Customer)
Issue Analytics
- State:
- Created a year ago
- Comments:36 (7 by maintainers)
Top GitHub Comments
As @svdHero and @derisen already figured out, the issue was related to our custom B2C policies. In the end, we were using
<OutputClaim ClaimTypeReferenceId="tenantId" AlwaysUseDefaultValue="true" DefaultValue="{Policy:TenantObjectId}">
in theSignUpOrSignin.xml
policy and didn’t assign the Output Claim “tenantId” in the Technical Profil for Session Management (“SM-RefreshTokenReadAndSetup”).Removing the default value from the policy and adding the output claim
<OutputClaim ClaimTypeReferenceId="tenantId"/>
to theSM-RefreshTokenReadAndSetup
Technical Profile solved the issue for us. Thanks for pointing in the right direction!By hint of @derisen (via email) I’ve just found out that the problem only occurs with internal user accounts, but not with local B2C user accounts. The internal user accounts are from our company AAD that is configured in B2C as an OpenID Connect identity provider.
I will ask our IT department to add a limited test user account for @derisen to our company AAD for further investigations.