[email protected] or @azure/[email protected]@azure/[email protected]@azure/[email protected]@azure/[email protected]@azure/[email protected]Important: Please fill in your exact version number above, e.g. [email protected].
Angular: I am testing the Angular 10 - MSAL Browser Sample
The Angular 10 - MSAL Browser Sample has been updated to include the ability to do a login redirect rather than a login popup. However using this login redirect, the post login redirect (in the sample set to http://localhost:4200) does not correctly refresh and the Angular Web App (or specifically MsalService) is not aware of the successful login. Calling this.authService.getAllAccounts().length in the AppComponent returns a length of 0. After refreshing the Chrome browser, the Web Application is able to process the successful login. In Safari browser the refresh also fails.
Issue is not present when login is provided with popup.
The sample uses @azure/msal-browser version 2.0.2, but I have tried 2.1.0 with the same results.
auth: {
clientId: '6226576d-37e9-49eb-b201-ec1eeb0029b6',
redirectUri: 'http://localhost:4200'
}
Angular 10 - MSAL Browser Samplenpm install and npm starthttp://localhost:4200 and start login redirect by clicking "Login"http://localhost:4200, note that the "Login" button still shows and the "Logout" button is hidden.Calling the Microsoft Graph in the /profile location will trigger a failing acquireToken call.
When I refresh Chrome, calling authService.getAllAccounts() returns the correct value. In Safari the browser refresh kicks off a failing acquireToken loop.
When I use interactionType: InteractionType.POPUP in the interceptor config, the post login redirect immediately shows the correctly refreshed Angular web app and the Logout button is showing rather than the Login button. So, this seems related to the InteractionType.REDIRECT configuration only.
@superman-lopez Unfortunately, this is a known issue with using redirect in the Angular sample, and we are currently researching ways to handle this better.
Some more observations on the login-redirect flow
I made the following adjustments to the sample app:
Protect the root-route also with MsalGuard:
const routes: Routes = [
// ...
{
path: '',
component: HomeComponent,
canActivate: [ MsalGuard ]
}
];
Moved the redirect-subscription from home.component.ts to app.component.ts:
// ...
ngOnInit(): void {
this.authService.handleRedirectObservable().subscribe({
next: (result) => console.log(result),
error: (error) => console.log(error)
});
// ...
With the above changes, the login-process gets stuck in infinite redirects between the application and login-authorization endpoint (e.g. login.microsoftonline.com), while reporting a state_mismatch error.
I also observed that authService.handleRedirectObservable() gets called whenever the page gets reloaded, however with different result's getting passed. The result is set to an instance of AuthenticationResult only for the actual login, but is set to null the other times.
Following is what gets logged in the browser's console, when you log off and try to log back in (line-breaks added to separate out the redirects):
Navigated to https://login.microsoftonline.com/common/oauth2/v2.0/logout?post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A4200%2F&client-request-id=afa9ee0b-a194-4114-b876-ebce9d04f75a
Navigated to https://login.microsoftonline.com/common/oauth2/v2.0/logoutsession
Navigated to http://localhost:4200/
Angular is running in development mode. Call enableProdMode() to enable production mode.
null
Navigated to https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=6226576d-37e9-49eb-b201-ec1eeb0029b6&scope=openid%20profile&redirect_uri=http%3A%2F%2Flocalhost%3A4200&client-request-id=533f5496-5574-4d74-8435-c28334d5898c&response_mode=fragment&response_type=code&x-client-SKU=msal.js.browser&x-client-VER=2.0.2&x-client-OS=&x-client-CPU=&client_info=1&code_challenge=vq-6mXhlIZqzxNH7W8k4OFAQszHcqMA7MD5gi5ul3mc&code_challenge_method=S256&nonce=0d63c6ff-99d7-48ea-9791-7aaa68c12058&state=eyJpZCI6ImVjNjBjZTMzLTkxMWItNDg5Mi05ODZlLWE2MjNlZDZlNTc4MCIsInRzIjoxNTk5ODgzMTM0LCJtZXRhIjp7ImludGVyYWN0aW9uVHlwZSI6InJlZGlyZWN0In19
Navigated to http://localhost:4200/
Angular is running in development mode. Call enableProdMode() to enable production mode.
ClientAuthError: state_mismatch: State mismatch error. Please check your network. Continued requests may cause cache overflow.
at ClientAuthError.AuthError [as constructor] (http://localhost:4200/vendor.js:69314:24)
at new ClientAuthError (http://localhost:4200/vendor.js:69603:28)
at Function.push../node_modules/@azure/msal-browser/dist/index.es.js.ClientAuthError.createStateMismatchError (http://localhost:4200/vendor.js:69667:16)
at ResponseHandler.push../node_modules/@azure/msal-browser/dist/index.es.js.ResponseHandler.validateServerAuthorizationCodeResponse (http://localhost:4200/vendor.js:72500:35)
at AuthorizationCodeClient.push../node_modules/@azure/msal-browser/dist/index.es.js.AuthorizationCodeClient.handleFragmentResponse (http://localhost:4200/vendor.js:72725:25)
at RedirectHandler.<anonymous> (http://localhost:4200/vendor.js:75296:52)
at step (http://localhost:4200/vendor.js:69193:23)
at Object.next (http://localhost:4200/vendor.js:69174:53)
at http://localhost:4200/vendor.js:69167:71
at new ZoneAwarePromise (http://localhost:4200/polyfills.js:973:33)
Navigated to https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=6226576d-37e9-49eb-b201-ec1eeb0029b6&scope=openid%20profile&redirect_uri=http%3A%2F%2Flocalhost%3A4200&client-request-id=04eeda6a-d2b9-47f5-b156-bd5dc948eb0d&response_mode=fragment&response_type=code&x-client-SKU=msal.js.browser&x-client-VER=2.0.2&x-client-OS=&x-client-CPU=&client_info=1&code_challenge=g9XxwoPGzzdd6wdZs-JK7f5maIICVKRAWhb1gb5EeaY&code_challenge_method=S256&nonce=28dbfc73-50d7-4c9c-ac60-3babbd7531ac&state=eyJpZCI6IjJiYmViMGVmLTlhNmYtNGRjNC1iOTkxLTkyZTkyOWUzZmYxNiIsInRzIjoxNTk5ODgzMzAyLCJtZXRhIjp7ImludGVyYWN0aW9uVHlwZSI6InJlZGlyZWN0In19
Navigated to http://localhost:4200/
Angular is running in development mode. Call enableProdMode() to enable production mode.
ClientAuthError: state_mismatch: State mismatch error. Please check your network. Continued requests may cause cache overflow.
at ClientAuthError.AuthError [as constructor] (http://localhost:4200/vendor.js:69314:24)
at new ClientAuthError (http://localhost:4200/vendor.js:69603:28)
at Function.push../node_modules/@azure/msal-browser/dist/index.es.js.ClientAuthError.createStateMismatchError (http://localhost:4200/vendor.js:69667:16)
at ResponseHandler.push../node_modules/@azure/msal-browser/dist/index.es.js.ResponseHandler.validateServerAuthorizationCodeResponse (http://localhost:4200/vendor.js:72500:35)
at AuthorizationCodeClient.push../node_modules/@azure/msal-browser/dist/index.es.js.AuthorizationCodeClient.handleFragmentResponse (http://localhost:4200/vendor.js:72725:25)
at RedirectHandler.<anonymous> (http://localhost:4200/vendor.js:75296:52)
at step (http://localhost:4200/vendor.js:69193:23)
at Object.next (http://localhost:4200/vendor.js:69174:53)
at http://localhost:4200/vendor.js:69167:71
at new ZoneAwarePromise (http://localhost:4200/polyfills.js:973:33)
Navigated to https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=6226576d-37e9-49eb-b201-ec1eeb0029b6&scope=openid%20profile&redirect_uri=http%3A%2F%2Flocalhost%3A4200&client-request-id=aba194f6-6e2b-403f-902a-0f3fdacdccfa&response_mode=fragment&response_type=code&x-client-SKU=msal.js.browser&x-client-VER=2.0.2&x-client-OS=&x-client-CPU=&client_info=1&code_challenge=bcnQeOm8LHwVX__mlX5mJO00AkempeOUNJRcPp-dXjo&code_challenge_method=S256&nonce=83273f51-2b1d-4515-ba4d-0f76c83fd92f&state=eyJpZCI6ImQxNWU4ZTIxLTIyOWEtNDA2My1iZDYwLWVjYWExZGIyNGIyYiIsInRzIjoxNTk5ODgzNDc4LCJtZXRhIjp7ImludGVyYWN0aW9uVHlwZSI6InJlZGlyZWN0In19
Navigated to http://localhost:4200/
...
...
...
@superman-lopez Unfortunately, this is a known issue with using redirect in the Angular sample, and we are currently researching ways to handle this better.
@jo-arroyo thanks for putting in the effort to improve this. Could I suggest to change back the default method to popup (InteractionType.POPUP) so that people that try the sample project don't immediately run into an issue?
@jo-arroyo Is there some place where those of us who want to track the progress of the Angular 10 wrapper for MSAL can see known issues, workarounds, and limitations? Most of the documentation I've seen out there is Angular 6-9 and I spent some time trying to get the current Angular 10 sample to work without any success.
@jkewley The current sample works for me when using InteractionType.POPUP as the login configuration. I have a repository where I integrate the sample project into a ASP.net project. Even if you have a different server side framework, you can look at the README to check for the Angular related configuration: https://github.com/superman-lopez/AzureAd_AspNet_Angular
I recommend you read the _Register Angular SPA on Azure AD_ paragraph.
@superman-lopez Your suggestion makes sense. We will change the default interaction type to Popup.
@jkewley Thanks for your feedback. The readme for the Angular 10 sample will be updated to reflect current known issues and limitations.
Exact same problem here... redirect method doesn't work correctly on Safari if The user doesn't have a session already (first Microsoft login).
Apparently the session data are not yet cached when handleRedirectObservable subscription streams for the existence of an Account in MsalGuard
Is there a workaround for this? I need to use Authoritzation-Code Flow, and I cannot use a popup as well.
Unfortunately, this is a known issue with using redirect in the Angular sample, and we are currently researching ways to handle this better.
@jo-arroyo , is there any workaround available for this in angular ?
Exact same problem here... redirect method doesn't work correctly on Safari if The user doesn't have a session already (first Microsoft login).
Apparently the session data are not yet cached when handleRedirectObservable subscription streams for the existence of an Account in MsalGuard
@brunnosantos Can you please clarify what you are referring to? Are you referring to this line in msal-guard?
https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/38bb90e4e94a8d89363d6266d91390d33910d2f0/samples/msal-angular-v2-samples/angular10-browser-sample/src/app/msal/msal.guard.ts#L63
I'm unable to reproduce the behavior you described with the latest version of the sample in Safari.
@mexmirror @jaami01 Please try the latest version of the sample from the dev branch, we made some changes so redirect should work now.
The only change I saw in the Angular 10 sample was an update to msal-browser to the latest version (v2.3.0), which did fix the issue for me.
Aside from that, after the user was redirected back to the site after signing in, the page would refresh itself repeatedly until I modified my main page's routing from "/" to an actual path "/dashboard" like so:

Exact same problem here... redirect method doesn't work correctly on Safari if The user doesn't have a session already (first Microsoft login).
Apparently the session data are not yet cached when handleRedirectObservable subscription streams for the existence of an Account in MsalGuard@brunnosantos Can you please clarify what you are referring to? Are you referring to this line in msal-guard?
I'm unable to reproduce the behavior you described with the latest version of the sample in Safari.
@mexmirror @jaami01 Please try the latest version of the sample from the dev branch, we made some changes so redirect should work now.
@jo-arroyo Yep, I was referring to that line! Safari was running its latest version with ITP enabled, BUT... my project is Angular v9! I forgot to mention that... =)
Btw, I've changed the strategy by using msal-browser 2.3.0 on APP_INITIALIZER as suggested on MS Msal 2 docs and everything works fine now. MS suggests using a strategy that avoids loading the entire angular app, then redirecting the user after the app is fully loaded. So basically, what I've managed to do was placing your code from canActivate method to a function that runs as an APP_INITIALIZER... therefore, before the angular app loads. On canActivate method, I've only kept the line
return !!this.authService.getAllAccounts().length;
meaning whoever it is, is authenticated.
I haven't tested this in depth, but the issue seems to be resolved when using the latest sample.
EDIT:
Although the sample works for me, in my actual application I can't make it work. After the redirect the browser opens the URL with the code, but it's not processed. The authentication ends on the page load and the observable msalSubject$ in MsalBroadcastService never emits anything.
The url stays as: https://localhost:5001/#code=0.AAAAQ2iT....
I have implemented the latest msal folder from the updated sample and all the details from app.component.ts and app.module.ts while using msal-browser 2.3.1. When I configure with InteractionType.POPUP it works as expected.
@superman-lopez In the sample application handleRedirectObservable() is only called in MsalGuard on canActivate. If your redirect page isn't protected with the MsalGuard then there will be never an event created after the login redirect. You could call handleRedirectObservable oninit in your component then the events should be created.
@andreasgloor thanks for the suggestion. I tried to protect my redirect with MsalGuard, and even though the url seems to no longer show the code the authentication doesn't actually work. Also in the sample project the redirect is not protected with MsalGuard and the authentication is successful when I try it there.
In the sample, which authenticates fine with the InteractionType.REDIRECT configuration:
const routes: Routes = [
// ...
{
path: '',
component: HomeComponent
}
];
while the redirect is configured as redirectUri: 'http://localhost:4200'.
Is there something I misunderstood from your message?
I was having problems using redirect interaction with hash routing enabled.
The workaround for it is to call this.authService.handleRedirectObservable() in app.component.ts (as mentioned by @gulench).
It must be called when the response code hash is still available the navigation bar, msal-browser uses window.location.href / window.location.hash and unfortunately does not allow overriding it when calling handleRedirectResponse(). I also needed to create a 'code' route, redirecting it to the main route, since angular tries to process the '#code=' as a route.
In the case where you need to call handleRedirectResponse() outside app.component, the workaround I found was to inject the response code hash string directly into the cache, in app.component.
const hashKey = this.browserStorage.generateCacheKey(TemporaryCacheKeys.URL_HASH);
this.browserStorage.setItem(hashKey, responseHash, CacheSchemaType.TEMPORARY);
The problem is the browserStorage class and some dependencies are not exported by msal-browser, so for testing, I needed to duplicate code from the lib into the angular project.
I am still having problems using hash routing when enabling 'navigateToLoginRequestUrl, where the MsalGuard code calls the authService.handleRedirectObservable() before the token is fully processed into the cache after the redirect. I believe there is a bug in ClientApplication/handleRedirectResponse(). The call to BrowserUtils.replaceHash(loginRequestUrl) happens before the handleHash(responseHash) promise is fulfilled. The navigation and the canActivate trigger runs before handleHash() does its job.
ClientApplication/handleRedirectResponse()
if (loginRequestUrlNormalized === currentUrlNormalized && this.config.auth.navigateToLoginRequestUrl) {
if (loginRequestUrl.indexOf("#") > -1) {
// Replace current hash with non-msal hash, if present
BrowserUtils.replaceHash(loginRequestUrl); // HERE
}
// We are on the page we need to navigate to - handle hash
return this.handleHash(responseHash);
}
It would be better if it is chained into the promise response:
if (loginRequestUrlNormalized === currentUrlNormalized && this.config.auth.navigateToLoginRequestUrl) {
// We are on the page we need to navigate to - handle hash
return this.handleHash(responseHash).then(r => {
if (loginRequestUrl.indexOf("#") > -1) {
// Replace current hash with non-msal hash, if present
BrowserUtils.replaceHash(loginRequestUrl);
}
return r;
});
}
For now, I am disabling navigateToLoginRequestUrl and implementing it manually.
@thiagodpaz Interesting, we'll take a look at this. cc: @tnorling
I am sorry for the late reply. I was able to come up with a workaround for the redirect flow. I was able to investigate further into the issue with the Guard. The problem is, that the promise of handleRedirect is resolving before the data is written into the storage (SessionStorage in my case). Since this is only an issue when initially acquiring the token e.g. the redirect happens, the observable should pass a response which at least contains an IdToken. So I added a second predicate to the condition if it should start the login process.
This would look like this inside canActivate of the Guard
return this.authService.handleRedirectObservable().pipe(
last(),
concatMap((result) => {
if (!this.authService.getAllAccounts().length && !result?.idToken) {
return this.loginInteractively(state.url);
}
return of(true);
}),
catchError(() => console.log)
);
I also added last(), since I am not sure if it is an implementation or a timing issue, but I am not sure if it is required.
I also added last(), since I am not sure if it is an implementation or a timing issue, but I am not sure if it is required.
@mexmirror What happens when you don't include last()?
@azure/msal-browser.
Most helpful comment
@brunnosantos Can you please clarify what you are referring to? Are you referring to this line in msal-guard?
https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/38bb90e4e94a8d89363d6266d91390d33910d2f0/samples/msal-angular-v2-samples/angular10-browser-sample/src/app/msal/msal.guard.ts#L63
I'm unable to reproduce the behavior you described with the latest version of the sample in Safari.
@mexmirror @jaami01 Please try the latest version of the sample from the dev branch, we made some changes so redirect should work now.