msal2:
The instructions and vanilla example app both show calling agent.handleRedirectPromise() after a redirect login. However, there is no method to determine if a there is a "pending" redirect to handle. The only way to determine that is to appear to login in again and catch an exception (BrowserAuthError, with code=interaction_in_progress). I'm assuming that the page behavior is app page => ms redirect login pages => app page.
This makes UI login logic more difficult to handle.
Is there a way to determine if a login is in progress and hence, one should not call login, but chain handleRedirectPromise()?
Or is that completely left up to the app e.g. set some "login processing" state into local storage?
It appears from the documentation that handleRedirectPromise() must be called after login redirect request as well as after token redirect request.
@aappddeevv handleRedirectPromise() is the function to use to determine whether or not you are in the middle of a redirect. This function will resolve to null if no auth hash is detected.
Also in the future please use the issue template.
I am also experiencing handleRedirectPromise() throwing this exception when the interaction is in progress:
Uncaught (in promise) BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API.
AuthError index.es.js:195
BrowserAuthError index.es.js:4657
createInteractionInProgressError index.es.js:4706
preflightInteractiveRequest index.es.js:7146
acquireTokenRedirect index.es.js:6721
step index.es.js:74
verb index.es.js:55
__awaiter index.es.js:48
__awaiter index.es.js:44
acquireTokenRedirect index.es.js:6716
loginRedirect index.es.js:6702
step index.es.js:74
verb index.es.js:55
__awaiter index.es.js:48
__awaiter index.es.js:44
loginRedirect index.es.js:6700
login AuthManager.ts:60
onLoginClick Login.tsx:7
React 42
tsx index.tsx:4
Webpack 5
It would be much nicer if the function either attempted to finish the in progress interaction and/or abandon it rather than throwing the exception.
I had to add additional "login prcessing state" outside the msal lib to manage login and the use of handleRedirectPromise. The vanilla example app uses external state as well but its very hard to see it. The issue is that handleRedirectPromise creates an async path to determine if you are in a redirect but the state to determine if that should be called is needed prior to the call in order to handle some login scenarios. Essentially, that 2 paths through code to determine the current login state for acquiring a token. That's also what the vanilla app shows.
It's also not clear if handleRedirectPromise should run after login or only after requesting a token. The example suggest that it should only run after token request and not after just a login request. It appears that agent.getAllAccounts() only returns values after login, so you can use that return value to determine if you have already logged in.
@aappddeevv handleRedirectPromise is the 2nd leg of the redirect flow, it should be invoked when you are using loginRedirect or acquireTokenRedirect. It should be called and awaited when you load your app before calling any other msal functions. If your app was not loaded as a result of redirection from AAD, it will immediately return null and you can continue with your other logic. You should not need to implement additional logic to determine when to call handleRedirectPromise. Can you explain a little more about your use case, why you feel you need to conditionally call handleRedirectPromise?
The flow is as follows: load your app -> handleRedirectPromise returns null and getAllAccounts returns empty array -> loginRedirect -> AAD Login/Consent page -> Your app -> handleRedirectPromise -> response
Or if you are already logged in and are trying to renew your tokens via redirect: acquireTokenRedirect -> AAD Login/Consent page -> Your app -> handleRedirectPromise -> response
@aappddeevv I'm not following at all.
vanilla example app uses external state as well but its very hard to see it.
Not sure what you mean by external state. The vanilla app calls handleRedirectPromise immediately after construction of the PublicClientApplication object, and then follows up execution based on the result of the promise. If you are attempting to use MSAL before the promise returned by the handleRedirectPromise() is resolved or rejected, then that is incorrect usage of the library. handleRedirectPromise() should be called immediately after the creation of the PublicClientApplication object and before any authentication calls are being made.
It's also not clear if handleRedirectPromise should run after login or only after requesting a token.
We've tried to be pretty explicit in docs and samples that this should be called anytime you are using redirect APIs on every page load, but I can try to make this more clear. handleRedirectPromise() has three possible code paths:
You should base your auth code off of these code paths. If the promise resolves to null, you can assume no login was made.
It might help if you could post a snippet of your code to show how you are using the library.
@wdspider that error isn't being thrown by handleRedirectPromise(). It's being thrown by loginRedirect() as shown by the stack trace. This is probably happening because loginRedirect() is being called before handleRedirectPromise() resolves.
It would be much nicer if the function either attempted to finish the in progress interaction and/or abandon it rather than throwing the exception.
That is exactly what handleRedirectPromise() is meant to do :)
I'll try my logic again and work to eliminate that external state flag (it was localStorage indicating that I had already called acquireTokenSilent). It does look like that indeed, handlePromiseRedirect should only be called after acquireTokenRedirect is called. I must have interpreted the docs incorrectly.
I had interpreted the docs to mean that handlePromiseRedirect should be called even after loginRedirect.
interface Props {}
function App(props: Props): React.ReactNode {
const [_, forceRender] = React.useState<number>(0)
const [message, changeMessage] = React.useState<string>()
const [errorMessage, changeErrorMessage] = React.useState<string>()
const [result, changeResult] = React.useState<any>()
// get first Account or null (works around small ts type bug in msal)
const account = getFirstAccount(agent)
React.useEffect(() => {
logger.debug("account: %O", account)
if (account) {
// we have an account, so login Ok, but need token
changeMessage("Completing login process...")
const loginState = getLoginState()
if (loginState !== "stage3") {
setLoginState("stage3")
// this either completes with an access token, completes with an error, or redirects away
getToken()
.then((response) => {
// if this completes, we have a token without redirecting!
sendMessage({ status: "success", response })
})
.catch((err) => {
logger.error("Error obtaining token", err)
changeMessage("Error logging in. Try again. (3)")
changeErrorMessage(err.message)
})
} else {
// we most likely redirected to get token, call handleRedirectPromise
// this either completes with an access token, a null value (it was not a redirect!) or an error
clearLoginState()
agent
.handleRedirectPromise()
.then((response) => {
logger.debug("handleRedirectPromise", response)
// can call parent dialog with success
if (!response) {
// there was no redirect but this was called
changeMessage("Unable to complete login process! Try again.")
changeErrorMessage("Promise returned null indicating there was not a redirect.")
// anything else to do?
} else {
// response was not null, we did return from redirect, return success to the parent dialog
sendMessage({ status: "success", response })
}
})
.catch((err) => {
logger.error("Error obtaining token", err)
changeMessage("Error obtaining token. Try again. (2)")
changeErrorMessage(err.message)
})
}
} else {
// no account available, hence, we need to still login
changeMessage(`Please wait, login processing... ${redirectUri}`)
const loginState = getLoginState()
if (loginState === "started") {
// this should never happen, getAllAccounts should return non-empty *if* login was successful??!?!?
changeMessage("Should never get here.")
} else if (!loginState) {
setLoginState("started")
login()
.then((result) => {
// redirect should have happened...so this block is irrelevant
})
.catch((err) => {
logger.error("Error logging in", err)
changeMessage("Error logging in. Try again. (1)")
})
} else {
clearLoginState()
changeMessage("Sorry, not sure what state of login processing we are in. Please start over.")
}
}
}, [account])
return (
<main style={{ height: "100%", display: "flex", flexDirection: "column" }}>
<h1>Login Processing</h1>
<Label>{message}</Label>
{errorMessage && (
<Text styles={{ root: { marginTop: "auto" } }} variant="smallPlus" block nowrap>
Error: {errorMessage}
</Text>
)}
</main>
)
}
and
async function login() {
return agent.loginRedirect({ ...token_request, redirectUri }).then(() => {
// the callback is not relevant since a redirect will change the current location
})
}
/** Try to acquire token silently and return it, otherwise a redirect is needed. */
async function getToken() {
return agent.acquireTokenSilent({ ...token_request, account: getFirstAccount(agent)! }).catch((err) => {
if (err instanceof msal.InteractionRequiredAuthError) {
// this will cause the page to navigate away
return agent.acquireTokenRedirect({ ...token_request, redirectUri })
} else {
return Promise.reject(err)
}
})
}
The end result for me is to call sendMessage which completes some a outlook add-in login process in an external web page and is not overly relevant to the example). Everything about logging and acquiring the token runs in a single html and tsx page including redirects. login status is stored in local storage. I tried to adjust my code to reflect not needing to call the redirect promise after login but only after access token acquisition.
I had interpreted the docs to mean that
handlePromiseRedirectshould be called even afterloginRedirect.
This is correct. handleRedirectPromise should be called when using any redirect API. e.g. loginRedirect and acquireTokenRedirect
@pkanher617, I have posted my AuthManager class in #2118 due to a different bug and this is my Login component:
import { AccountInfo } from '@azure/msal-browser';
import { Button, Typography } from '@material-ui/core';
import React, { FC, useCallback, useState, useEffect } from 'react';
import { AuthManager } from '../../managers/AuthManager';
export const Login: FC = () => {
const onLoginClick = useCallback(() => {
AuthManager.login();
}, []);
const currentAccount: AccountInfo | undefined = AuthManager.getAccount();
const [userName, setUserName] = useState<string | undefined>(undefined);
useEffect(() => {
setUserName(currentAccount?.username);
}, [currentAccount]);
console.log('un', userName);
if (userName === undefined) {
return <Button onClick={onLoginClick}>Login</Button>;
}
return <Typography variant="body1">Welcome {userName}</Typography>;
};
which only calls the login method when the button is clicked. Can you instruct me on what needs to be changed in order to get handleRedirectPromise to work like you state?
Hmm...well I think I need to play with this more than. I had remove the handleRedirectPromise after the login (per my code above). If it is to be called after any redirect call, I'm not sure at first blush if I will know if its been called after a login redirect or a get token redirect. The presence of an account will not tell me that.
@aappddeevv Why do you need to know whether or not it is a login or get token? Shouldn't it be based off of the scopes in the response?
In MSAL there is no difference between login and acquireToken.

I agree that a token is a token. I do pass in the same scopes to both login redirect and the "get token" redirect. I'll have to look up if that's not needed under the code authorization flow and then decide if I can differentiate with that. I think your general thought is that I should be able to differentiate on the login processing state based solely on the token returned at each step, and that seems to make sense and potentially is the missing piece for me.
@wdspider I looked at the AuthManager.ts class as well as the code you posted above. You need some way to base the initialization of the app on the handleRedirectPromise(). I.E. disable buttons or show some dialog until the promise resolves in the initialize() function, then allow users to call the auth functions.
@wdspider I looked at the AuthManager.ts class as well as the code you posted above. You need some way to base the initialization of the app on the
handleRedirectPromise(). I.E. disable buttons or show some dialog until the promise resolves in theinitialize()function, then allow users to call the auth functions.
hmmz.... ic. I guess I'll have to think up a solution to that. A Typescript / React QuickStart demo example would have been very helpful in sorting this all out and made the library feel much more "plug-n-play".
I'm still trying this but can't get rid of external state yet.
My scenario is a single page that automatically logs in and acquires a token. Ideally, both steps must be executed purely off the state of progress of logging in via redirect. I did see that I can add a "state" value for the first login and track it that way inside handleRedirectPromise. However, the issue is that for a react component, I have to be to re-render the component and but it needs to know if an access token request (silently) has already been made after the login succeeds. Otherwise it reissues the get token request which if it can complete silently, does not need a redirect. Hence I still have 2 possible code paths and msal/oauth only directly supports state in one the paths.
Since the issue only arises when asking for an access token silently first before getting an access token via redirect, it really isn't a msal library concern and I will always need external state to track whether the silent request has been made on a re-render. So instead of msal 1.x's getLoginInProgress(), the app just needs to track it.
I think this issue can close because under a pure redirection model, there is enough state.
Just as a side comment, I stuck all the code I have into the callback of handleRedirectPromise, detect when the result is null/non-null then base the next step on that information. I also placed the strings "login" and "redirect" into the state var of the redirect request options so I knew what the result pertained to when the result was non-null. Since I immediately need to fetch the token after login (unique to my use-case), I had some other steps to force a re-render once a result is obtained, but that's another topic.
Is there going to be no equivalent to getLoginInProgress in msal 2.X? like there was in msal 1.X? It is very useful for us to avoid having to track the external state.
If you want to know if you are in a login step (vs access token step), you can pass state="login" to the login request options and check the state inside the handleRedirectPromise. Similarly, if you are issuing an access token request, you can set state="token" and check for that inside handleRedirectPromise.
@aappddeevv Yeah you could, what I ended up doing was only calling loginRedirect / loginPopup inside handleRedirectPromise (when there was also no user account i.e a new "session")). Then making sure elsewhere where I was calling msal functions to make sure I already had an account.
I couldn't get around the extra load of the page needed e.g running something like window.location.reload. (At least for me since I am using mostly vanilla js)
I call everything inside handleRedirectPromise as well. Inside my handler, I check for a null account to see if I'm already logged in. If not, I call loginRedirect. If I have a redirect result (which means I at least have an account), then either I've finished processing the login redirect or the token redirect and act accordingly. I always ask for a silent token first so I sometimes avoid the token redirect. I don't think you need handleRedirectPromise with loginPopup though.
Be careful with always calling silent token function, if you call that too early e.g before handleRedirectPromise resolves you will get an error. You should make sure you have an account available before you call the silent redirect, if you have no users you cannot call the silent redirect if I remember correctly.
I did run into that :-). With the account not null check, I always ensure I'm logged in. At least that's I think (no null account => logged in).
This issue has not seen activity in 14 days. It will be closed in 7 days if it remains stale.
This issue has been closed due to inactivity. If this has not been resolved please open a new issue. Thanks!