AzureAD / microsoft-authentication-library-for-js

Microsoft Authentication Library (MSAL) for JS
http://aka.ms/aadv2
MIT License
3.69k stars 2.65k forks source link

token renewal operation failed due to timeout MSAL #1592

Closed ashishbhulani closed 4 years ago

ashishbhulani commented 4 years ago

Hi,

Library : "@azure/msal-angular": "^1.0.0-beta.5" "msal": "^1.2.2"

I have integrated MSAL library with my angular 8 application. Everything works fine except i keep getting an error token renewal operation failed due to timeout as soon as the token is expired. I was wondering how to fix this.

Below is the MSAL_config that i am using :

"auth": {

        "clientId": "xxxxx",
        "authority": "https://login.microsoftonline.com/xxx",
        "validateAuthority":"true",
        "postLogoutRedirectUri": "http://localhost:4200/",
        "navigateToLoginRequestUrl": true,
        "redirectUri":"http://localhost:4200/"
    },

    "scopes":["user.read", "openid", "profile"],
    "popUp": false,
    "unprotectedResources": ["https://www.microsoft.com/en-us/"],
    "protectedResourceMap":[["https://graph.microsoft.com/v1.0/me", ["user.read"]]],
    "system":{
        "loadFrameTimeout":10000
    },
    "cache": {
      "cacheLocation": "localStorage",
      "storeAuthStateInCookie":true

    }

I have created a new token interceptor which pull the token everytime a http request is amde. Below is the code, here i face issue when the token is expired and i get the error. Please help me to resolve this as it is affecting the project.

if (this.authService.getAccount()) {

                let token: string;
                return from(
                    this.authService.acquireTokenSilent(this.loginRequest)
                        .then((response: AuthResponse) => {

                            token = response.idToken.rawIdToken;
                            const authHeader = `Bearer ${token}`;
                            if (!request.headers.has('Cache-Control')) {
                                request = request.clone({ headers: request.headers.set('Cache-Control', 'no-cache' + '') });
                            }

                            if (!request.headers.has('Pragma')) {
                                request = request.clone({ headers: request.headers.set('Pragma', 'no-cache' + '') });
                            }

                            if (!request.headers.has('Expires')) {
                                request = request.clone({ headers: request.headers.set('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT' + '') });
                            }
                            return request.clone({
                                setHeaders: {
                                    Authorization: authHeader,
                                }
                            });

                        })
                )
                    .pipe(
                        mergeMap(nextReq => next.handle(nextReq)),
                        tap(
                            event => { },
                            err => {
                                if (err) {
                                    var iframes = document.querySelectorAll('iframe');
                                    for (var i = 0; i < iframes.length; i++) {
                                        iframes[i].parentNode.removeChild(iframes[i]);
                                    }
                                    debugger

                                    this.authService.handleRedirectCallback((err: AuthError, response) => {
                                        debugger
                                        this.authService.loginRedirect();
                                        if (err) {

                                            console.error('Redirect Error: ', err.errorMessage);
                                            return;
                                        }
                                        debugger
                                        this.authService.loginRedirect();
                                        console.log('Redirect Success: ', response);
                                    });
                                    this.broadcastService.broadcast('msal:notAuthorized', err.message);
                                }
                            }
                        )
                    );
            }

So in the above code it goes to the error but it never enters this.authService.handleRedirectCallback can someone please help me with this. I need the token to be renewed.

pkanher617 commented 4 years ago

@ashishbhulani Could you please use the template when creating an issue? Without knowing what library and version you are using it is difficult to understand your issue.

ashishbhulani commented 4 years ago

@pkanher617 How do i use the template? Any ways i have added the library that i am using.

pkanher617 commented 4 years ago

@ashishbhulani when you are creating a new issue, you can choose one of the options to give you the relevant template.

It appears you are calling handleRedirectCallback in the wrong place. It needs to be set directly after the constructor of the authService, as it gets called when the page is loaded again with the auth information.

ashishbhulani commented 4 years ago

@pkanher617 can you help me modify the code what exactly i am doing wrong. I have written this code inside tojen interceptor service.

pkanher617 commented 4 years ago

@ashishbhulani I just told you. Directly after the creating the authService object on page load, you need to call handleRedirectCallback. Currrently you are doing it after an error in acquireTokenSilent, which is incorrect usage.

We have some angular dev samples in the repo you can use for more information.

ashishbhulani commented 4 years ago

@pkanher617 Actually the issue is that once the token is getting expired i am unable to renew it and that is the reason why i have put in error. So when the token is not expired the acquiretokensilent helps me to authenticate with the api. I face the token renew issue only when it is expired. So can you help me to re-write this code or can you paste here an example on how to fix this in this situation. As i have already gone through the demo samples but i dont see the usage of this. I request you to help me with the code.

ashishbhulani commented 4 years ago

@jasonnutter Can you help me on this?

ashishbhulani commented 4 years ago

@pkanher617 @jasonnutter How do i renew my token. can you please help me with that?

jasonnutter commented 4 years ago

@ashishbhulani You should be able to handle this without modifying the interceptor. When you make the http request and you receive an error, if it is a timeout or interaction required error, invoke acquireTokenPopup with the same set of scopes, and then make the request again.

const GRAPH_ENDPOINT = "https://graph.microsoft.com/v1.0/me";

this.http.get(GRAPH_ENDPOINT)
    .subscribe({
      next: (profile) => {
        this.profile = profile;
      },
      error: (err: AuthError) => {
          this.authService.acquireTokenPopup({
            scopes: this.authService.getScopesForEndpoint(GRAPH_ENDPOINT)
          })
          .then(() => {
            this.http.get(GRAPH_ENDPOINT)
              .toPromise()
              .then(profile => {
                this.profile = profile;
              });
          });
      }
    });

Note, if you want to use acquireTokenRedirect or loginRedirect instead, your application will need to implement handleRedirectCallback separately, not inside the interceptor or where you make the http request. Instead, it needs to be invoked on page load, as demonstrated in the Angular 8 sample. Remember, if you call redirect, the browser will fully redirect away from your application and lose all context, which is why we recommend acquireTokenPopup instead for this scenario.

ashishbhulani commented 4 years ago

@jasonnutter This worked but now when i acquiretokensilently i am getting the old token where as in local storage i see the new token. this.authService.acquireTokenSilent(this.loginRequest) .then((response: AuthResponse)

In the response i still see the old token what could be the reason?

ashishbhulani commented 4 years ago

@jasonnutter can you tell what is the actual issue here?

ashishbhulani commented 4 years ago

@jasonnutter can you please help me. I am stuck with renewal of token?

github-actions[bot] commented 4 years ago

This issue has not seen activity in 14 days. It may be closed if it remains stale.

jasonnutter commented 4 years ago

@jasonnutter This worked but now when i acquiretokensilently i am getting the old token where as in local storage i see the new token. this.authService.acquireTokenSilent(this.loginRequest) .then((response: AuthResponse)

In the response i still see the old token what could be the reason?

What is the value of this.loginRequest?

We've heard some other reports of this happening, I'll follow up if I need more information.

eeskildsen commented 4 years ago

@jasonnutter I'm getting the same error. msal 1.3.1., msal-angular 1.0.0.

Auth only works right after I clear the browser cache. Hours later:

ERROR Error: Uncaught (in promise): ClientAuthError: URL navigated to is https://login.microsoftonline.com/<TenantId>/oauth2/v2.0/authorize?response_type=id_token&scope=openid%20profile&client_id=<ClientId>&redirect_uri=<RedirectUri>&state=<State>&nonce=<Nonce>&client_info=1&x-client-SKU=MSAL.JS&x-client-Ver=1.3.1&login_hint=<MyUsername>&client-request-id=<RequestId>&prompt=none&response_mode=fragment, Token renewal operation failed due to timeout.

Here's my code. There are 3 places where I'm configuring/calling MSAL. I don't think I'm doing anything fancy. I'm basing it on the Angular 8 sample app.

  1. app.module.ts -> NgModule -> imports:

    MsalModule.forRoot({ auth: { clientId: "", authority: "https://login.microsoftonline.com//", validateAuthority: true, redirectUri: '', postLogoutRedirectUri: "", navigateToLoginRequestUrl: true,
    }, cache: { cacheLocation : "localStorage", storeAuthStateInCookie: isIE }, framework: { unprotectedResources: ['https://www.microsoft.com/en-us/'], } }, { popUp: !isIE, consentScopes: [ "https://graph.microsoft.com/User.Read", "api:///API" ], extraQueryParameters: {}, protectedResourceMap: [ ['', ['api:///API']], ['https://graph.microsoft.com', ['user.read', 'openid', 'profile', 'email', 'offline_access']] ] })

  2. app.module.ts -> NgModule -> providers

    { provide: HTTP_INTERCEPTORS, useClass: MsalInterceptor, multi: true }

  3. app.component.ts -> AppComponent -> ngOnInit

    ngOnInit() { this.authService.handleRedirectCallback((authError, response) => { if (authError) { console.error('Redirect Error: ', authError.errorMessage); return; }

           console.log('Redirect Success: ', response);
       });

    }

ryandegruyter commented 4 years ago

@jasonnutter We're also experience a token renewal problem. We have our handleRedirectCallback setup in the root component: this.msalService.handleRedirectCallback((authError, response) => { this.authError = authError; if (authError && !this.isLoginInProgress()) { this.router.navigate(['login-failed']); } });

Error:

ERROR ClientAuthError: URL navigated to is https://login.microsoftonline.com/c89214f1-7515-48ee-9cd0-9b859ed3e4c4/oauth2/v2.0/authorize?response_type=id_token&scope=openid%20profile&client_id=xx-1916-43a3-bdab-a808a9fc29cd&redirect_uri=http%3A%2F%2Flocalhost%3A4200&state=$$OUR_TOKEN_REMOVED$$&nonce=ba4d0a17-xxxx-4112-b997-5d79f8d483db&client_info=1&x-client-SKU=MSAL.JS&x-client-Ver=1.3.1&login_hint=JE34524%40ACC-xxx-xxx.COM&client-request-id=fa6cc7f8-xxxx-474d-aa05-b256ffa7b29c&prompt=none&response_mode=fragment, Token renewal operation failed due to timeout. at ClientAuthError.AuthError [as constructor] (http://localhost:4200/vendor.js:190343:28) at new ClientAuthError (http://localhost:4200/vendor.js:190481:28) at Function.push../node_modules/msal/lib-es6/error/ClientAuthError.js.ClientAuthError.createTokenRenewalTimeoutError

Also why doesn't the callback handle the error?

ryandegruyter commented 4 years ago

Also, a hard refresh of the page and token retrieval works.

jasonnutter commented 4 years ago

@eeskildsen @ryandegruyter Thanks, that is helpful. Seems to be potential timeout issues when ID tokens are requested. We'll investigate and follow up, thanks!

ryandegruyter commented 4 years ago

@jasonnutter thanks, I have some additional logs, 3 different users, here is one:

VM1624 main.js:10924 Wed, 10 Jun 2020 15:44:10 GMT:1.3.1-Verbose location change event from old url to new url | Level: 3 | ContainsPii: false
VM1624 main.js:10924 Wed, 10 Jun 2020 15:44:10 GMT:1.3.1-Verbose Token is not in cache for scope:xxxxxxxx-1916-43a3-bdab-a808a9fc29cd | Level: 3 | ContainsPii: false
VM1624 main.js:10924 Wed, 10 Jun 2020 15:44:10 GMT:1.3.1-Verbose renewing idToken | Level: 3 | ContainsPii: false
VM1624 main.js:10924 Wed, 10 Jun 2020 15:44:10 GMT:1.3.1-Info renewidToken is called | Level: 2 | ContainsPii: false
VM1624 main.js:10924 Wed, 10 Jun 2020 15:44:10 GMT:1.3.1-Info Add msal frame to document:msalIdTokenFrame|xxxxxxxx-1916-43a3-bdab-a808a9fc29cd|undefined | Level: 2 | ContainsPii: false
VM1624 main.js:10924 Wed, 10 Jun 2020 15:44:10 GMT:1.3.1-Verbose Renew Idtoken Expected state: eyJpZCI6ImY0ZjQwNzg2LThjMWEtNDM3MS1hMTljLWFiYmZiMjBkZWE4MyIsInRzIjoxNTkxODAzODUxLCJtZXRob2QiOiJzaWxlbnRJbnRlcmFjdGlvbiJ9 | Level: 3 | ContainsPii: false
VM1624 main.js:10924 Wed, 10 Jun 2020 15:44:10 GMT:1.3.1-Verbose Set loading state to pending for: xxxxxxxx-1916-43a3-bdab-a808a9fc29cd|undefined:eyJpZCI6ImY0ZjQwNzg2LThjMWEtNDM3MS1hMTljLWFiYmZiMjBkZWE4MyIsInRzIjoxNTkxODAzODUxLCJtZXRob2QiOiJzaWxlbnRJbnRlcmFjdGlvbiJ9 | Level: 3 | ContainsPii: false
VM1624 main.js:10924 Wed, 10 Jun 2020 15:44:10 GMT:1.3.1-Info LoadFrame: msalIdTokenFrame|xxxxxxxx-1916-43a3-bdab-a808a9fc29cd|undefined | Level: 2 | ContainsPii: false
VM1623 vendor.js:222005 [WDS] Live Reloading enabled.
VM1624 main.js:10924 Wed, 10 Jun 2020 15:44:11 GMT:1.3.1-Info Add msal frame to document:msalIdTokenFrame|xxxxxxxx-1916-43a3-bdab-a808a9fc29cd|undefined | Level: 2 | ContainsPii: false
environment.ts:34 Wed, 10 Jun 2020 15:44:16 GMT:1.3.1-Verbose Loading frame has timed out after: 6 seconds for scope/authority xxxxxxxx-1916-43a3-bdab-a808a9fc29cd|undefined:eyJpZCI6IjAzYTQ0YmU5LWViNTAtNDhkMi05ZDNiLWJmNGJjY2Y4NGFkMCIsInRzIjoxNTkxODAzODQ4LCJtZXRob2QiOiJzaWxlbnRJbnRlcmFjdGlvbiJ9 | Level: 3 | ContainsPii: false
3environment.ts:34 Wed, 10 Jun 2020 15:44:16 GMT:1.3.1-Error Error when acquiring token for scopes: xxxxxxxx-1916-43a3-bdab-a808a9fc29cd ClientAuthError: URL navigated to is https://login.microsoftonline.com/c89214f1-7515-48ee-9cd0-9b859ed3e4c4/oauth2/v2.0/authorize?response_type=id_token&scope=openid%20profile&client_id=xxxxxxxx-1916-43a3-bdab-a808a9fc29cd&redirect_uri=http%3A%2F%2Flocalhost%3A4200&state=eyJpZCI6IjAzYTQ0YmU5LWViNTAtNDhkMi05ZDNiLWJmNGJjY2Y4NGFkMCIsInRzIjoxNTkxODAzODQ4LCJtZXRob2QiOiJzaWxlbnRJbnRlcmFjdGlvbiJ9&nonce=ac7bcf28-baf9-48f5-94d1-2520a6d0030f&client_info=1&x-client-SKU=MSAL.JS&x-client-Ver=1.3.1&login_hint=xxxxxxxx%40ACC-xxxxxxxx.COM&client-request-id=585c2795-9f0d-47d7-a316-444a267f59f0&prompt=none&response_mode=fragment, Token renewal operation failed due to timeout. | Level: 0 | ContainsPii: false
environment.ts:34 Wed, 10 Jun 2020 15:44:16 GMT:1.3.1-Error Non-interaction error in MSAL Guard: URL navigated to is https://login.microsoftonline.com/c89214f1-7515-48ee-9cd0-9b859ed3e4c4/oauth2/v2.0/authorize?response_type=id_token&scope=openid%20profile&client_id=xxxxxxxx-1916-43a3-bdab-a808a9fc29cd&redirect_uri=http%3A%2F%2Flocalhost%3A4200&state=eyJpZCI6IjAzYTQ0YmU5LWViNTAtNDhkMi05ZDNiLWJmNGJjY2Y4NGFkMCIsInRzIjoxNTkxODAzODQ4LCJtZXRob2QiOiJzaWxlbnRJbnRlcmFjdGlvbiJ9&nonce=ac7bcf28-baf9-48f5-94d1-2520a6d0030f&client_info=1&x-client-SKU=MSAL.JS&x-client-Ver=1.3.1&login_hint=xxxxxxxx%40ACC-xxxxxxxx.COM&client-request-id=585c2795-9f0d-47d7-a316-444a267f59f0&prompt=none&response_mode=fragment, Token renewal operation failed due to timeout. | Level: 0 | ContainsPii: false
core.js:4002 ERROR Error: Uncaught (in promise): ClientAuthError: URL navigated to is https://login.microsoftonline.com/c89214f1-7515-48ee-9cd0-9b859ed3e4c4/oauth2/v2.0/authorize?response_type=id_token&scope=openid%20profile&client_id=xxxxxxxx-1916-43a3-bdab-a808a9fc29cd&redirect_uri=http%3A%2F%2Flocalhost%3A4200&state=eyJpZCI6IjAzYTQ0YmU5LWViNTAtNDhkMi05ZDNiLWJmNGJjY2Y4NGFkMCIsInRzIjoxNTkxODAzODQ4LCJtZXRob2QiOiJzaWxlbnRJbnRlcmFjdGlvbiJ9&nonce=ac7bcf28-baf9-48f5-94d1-2520a6d0030f&client_info=1&x-client-SKU=MSAL.JS&x-client-Ver=1.3.1&login_hint=xxxxxxxx%40ACC-xxxxxxxx.COM&client-request-id=585c2795-9f0d-47d7-a316-444a267f59f0&prompt=none&response_mode=fragment, Token renewal operation failed due to timeout.
ClientAuthError: URL navigated to is https://login.microsoftonline.com/c89214f1-7515-48ee-9cd0-9b859ed3e4c4/oauth2/v2.0/authorize?response_type=id_token&scope=openid%20profile&client_id=xxxxxxxx-1916-43a3-bdab-a808a9fc29cd&redirect_uri=http%3A%2F%2Flocalhost%3A4200&state=eyJpZCI6IjAzYTQ0YmU5LWViNTAtNDhkMi05ZDNiLWJmNGJjY2Y4NGFkMCIsInRzIjoxNTkxODAzODQ4LCJtZXRob2QiOiJzaWxlbnRJbnRlcmFjdGlvbiJ9&nonce=ac7bcf28-baf9-48f5-94d1-2520a6d0030f&client_info=1&x-client-SKU=MSAL.JS&x-client-Ver=1.3.1&login_hint=xxxxxxxx%40ACC-xxxxxxxx.COM&client-request-id=585c2795-9f0d-47d7-a316-444a267f59f0&prompt=none&response_mode=fragment, Token renewal operation failed due to timeout.
    at ClientAuthError.AuthError [as constructor] (AuthError.js:22)
    at new ClientAuthError (ClientAuthError.js:105)

Error logs are also different for each user, there is one user using Safari, and he's not able to log in at all. Keep's getting the timeout error.

I could provide you with more logs, from each user. but best by mail.

eeskildsen commented 4 years ago

As a workaround, I set loadFrameTimeout to 30000. That let me through.

MsalModule.forRoot({
    system: {
        loadFrameTimeout: 30000
    },
    ...

Edit: I noticed redirectUri was wrong in one of my earlier code pushes. Maybe that was the real culprit.

ryandegruyter commented 4 years ago

Nope not working here with timeout:

Wed, 17 Jun 2020 15:09:30 GMT:1.3.1-Verbose Loading frame has timed out after: 30 seconds for scope/authority 7e20f2ad-1916-43a3-bdab-a808a9fc29cd|undefined:eyJpZCI6ImQwNDU1YjllLWE0NTItNDIwMS1hNDE0LWIwMTgyMGFmNDMzMiIsInRzIjoxNTxxxxdGlvbiJ9 | Level: 3 | ContainsPii: false

Something undefined here though.

@jasonnutter if you have any leads for us? This is deployed to more than then thousand of users and we have no pattern. Some users experience it, others not...

Clicking on the link in the error message only redirects us to the redirectUri.

ryandegruyter commented 4 years ago

I noticed this request fails when it happens:

curl 'https://login.microsoftonline.com/c89214f1-7515-48ee-xxxx-9b859ed3e4c4/oauth2/v2.0/authorize?response_type=id_token&scope=openid%20profile&client_id=7e20f2ad-1916-43a3-bdab-a808a9fc29cd&redirect_uri=http%3A%2F%2Flocalhost%3A4200&state=eyJpZCI6IjlmZjgzYzY0LTk1ZTktNGUzOS05YjI0xxxxxkyNDY0Mzc0LCJtZXRob2QiOiJzaWxlbnRJbnRlcmFjdGlvbiJ9&nonce=2fb4194f-xxx-4f74-b14d-968c2304ed3d&client_info=1&x-client-SKU=MSAL.JS&x-client-Ver=1.3.1&login_hint=JE26806%40ACC-xxx-xxxx.COM&client-request-id=6ee72ee5-xxx-42bd-9028-21aa5eaa619f&prompt=none&response_mode=fragment'
patrasm commented 4 years ago

I was fighting with this issue for the past week. I ended up implementing a solution similar to this:

https://github.com/Azure-Samples/active-directory-javascript-graphapi-v2/blob/60e53cf861c0a51a690aff8043032dd94f1b488d/JavaScriptSPA/authRedirect.js

github-actions[bot] commented 4 years ago

This issue has not seen activity in 14 days. It may be closed if it remains stale.

anth-git commented 4 years ago

I'm still encountering "Token renewal operation failed due to the timeout" error in v1.3.3. It fails roughly 1 in 4 login attempts.

When it fails:

but at the same time:

abellisai commented 4 years ago

Me too, i'm still encountering this error when MSAL (v1.3.3) rise "monitorIframeForHash unable to find hash in url, timing out"

llakhani commented 4 years ago

Yes, I'm facing same issue - 'token renewal operation failed due to timeout token renewal failed'. Any update ?

ghost commented 4 years ago

This is occurring for our project as well. Is there any additional information we can provide? Increasing the timeout does not resolve the issue. Logging in is fine, but fails when calling our function app API from the site.

anth-git commented 4 years ago

I logged the value of the href variable in the monitorIframeForHash function.

When it works:

about:blank (several times)
http://localhost:4200/#state=eyJ…

When it doesn't work:

about:blank (several times)
http://localhost:4200/# (without anything after the #)

The problem might be with the POLLING_INTERVAL_MS. If I increase it from the default 50ms to 500ms I'm not able to sign in whatsoever. If I decrease it to 5ms, it seems to be working every time.

So it seems that something is clearing the query string from the URL before the library extracts it (the query string is available for less than 50ms), and it's based on a pure chance whether it works or not.

ghost commented 4 years ago

I logged the value of the href variable in the monitorIframeForHash function.

When it works:

about:blank (several times)
http://localhost:4200/#state=eyJ…

When it doesn't work:

about:blank (several times)
http://localhost:4200/# (without anything after the #)

The problem might be with the POLLING_INTERVAL_MS. If I increase it from the default 50ms to 500ms I'm not able to sign in whatsoever. If I decrease it to 5ms, it seems to be working every time.

So it seems that something is clearing the query string from the URL before the library extracts it (the query string is available for less than 50ms), and it's based on a pure chance whether it works or not.

Can you share how this was changed?

jasonnutter commented 4 years ago

I logged the value of the href variable in the monitorIframeForHash function.

When it works:

about:blank (several times)
http://localhost:4200/#state=eyJ…

When it doesn't work:

about:blank (several times)
http://localhost:4200/# (without anything after the #)

The problem might be with the POLLING_INTERVAL_MS. If I increase it from the default 50ms to 500ms I'm not able to sign in whatsoever. If I decrease it to 5ms, it seems to be working every time.

So it seems that something is clearing the query string from the URL before the library extracts it (the query string is available for less than 50ms), and it's based on a pure chance whether it works or not.

@anth-git This would indicate that something is changing/removing the hash before the library has a chance to parse it. Are you using hash routing in your Angular application?

ghost commented 4 years ago

I logged the value of the href variable in the monitorIframeForHash function. When it works:

about:blank (several times)
http://localhost:4200/#state=eyJ…

When it doesn't work:

about:blank (several times)
http://localhost:4200/# (without anything after the #)

The problem might be with the POLLING_INTERVAL_MS. If I increase it from the default 50ms to 500ms I'm not able to sign in whatsoever. If I decrease it to 5ms, it seems to be working every time. So it seems that something is clearing the query string from the URL before the library extracts it (the query string is available for less than 50ms), and it's based on a pure chance whether it works or not.

@anth-git This would indicate that something is changing/removing the hash before the library has a chance to parse it. Are you using hash routing in your Angular application?

Regardless of @anth-git 's answer. We are having the same issue and using hash routing. It's required for our angular project to work correctly with Azure Static Web Apps Preview (justifying our reasoning).

1447 (issue), #1452 (pr) - MSAL Guard with hash routing should be supported.

anth-git commented 4 years ago

@ZacharyHiggins-dbMotion I changed it in node_modules\msal\lib-es6\utils\WindowUtils.js (obviously just for investigation purposes)

@jasonnutter Yes, I'm using hash location strategy in the Angular app.

anth-git commented 4 years ago

I turned off the hash routing and the behavior is the same, so I don't think it's related to the routing strategy.

anth-git commented 4 years ago

I think I managed to get it working even for high POLLING_INTERVAL_MS values:

@NgModule({
  imports: [RouterModule.forRoot(routes,
    {
      enableTracing: false,
      useHash: true,
      initialNavigation: isInIframe() ? 'disabled' : undefined // <-THIS
    })],
  exports: [RouterModule]
})
export class AppRoutingModule {
}

export function isInIframe() {
  return window !== window.parent && !window.opener;
}
abellisai commented 4 years ago

Hi @anth-git Thanks for your suggestion testing on my personal env, it works!

ghost commented 4 years ago

I think I managed to get it working even for high POLLING_INTERVAL_MS values:

@NgModule({
  imports: [RouterModule.forRoot(routes,
    {
      enableTracing: false,
      useHash: true,
      initialNavigation: isInIframe() ? 'disabled' : undefined // <-THIS
    })],
  exports: [RouterModule]
})
export class AppRoutingModule {
}

export function isInIframe() {
  return window !== window.parent && !window.opener;
}

https://angular.io/api/router/InitialNavigation

This makes sense. Nice find. I'm testing this locally as well. So far so good. Will follow up tomorrow (unless the problem returns before then) with an update. Thanks!

jasonnutter commented 4 years ago

Yes, preventing navigation when you are in an iframe is a way to mitigate the behavior from the router that removes the hash. We show a similar workaround in the samples, but we'll make sure this is better documented.

piotrduperas commented 4 years ago

Hello @jasonnutter ! We are experiencing the same problem. This is one of our logs from console: Wed, 29 Jul 2020 09:42:43 GMT:1234-1.3.3-Verbose-pii RenewToken scope and authority: https://<our.scope>|undefined I have investigated this undefined, it is a request.authority field, but I was unable to trace it further.

We have found that this can be caused by MsalGuard on redirect page, but removing it and calling msalService.loginRedirect() does not work. The suggestion by @anth-git does not work for us as well.

anth-git commented 4 years ago

@piotrduperas Strange it doesn't work. It's pretty foolproof for me now. Do you use approach described in https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-js-avoid-page-reloads ? Also make sure you don't call any msal methods (like loginRedirect, acquireTokenSilent) in an iframe.

piotrduperas commented 4 years ago

@anth-git We are observing that this approach works on Firefox, but on Chrome and Edge it works only sometimes, however, checking "Disable cache" in Dev Tools results in much rarer occurrences of this error. We tried to use some interceptor that sets browser flags concerned about cache, but it did not work as well.

alvipeo commented 4 years ago

Does anybody know if this issue exists in 2.0?

alvipeo commented 4 years ago

@anth-git I just added MsalComponent and it still doesn't work with my live.com account :/

arnevdv commented 4 years ago

@ashishbhulani You should be able to handle this without modifying the interceptor. When you make the http request and you receive an error, if it is a timeout or interaction required error, invoke acquireTokenPopup with the same set of scopes, and then make the request again.

const GRAPH_ENDPOINT = "https://graph.microsoft.com/v1.0/me";

this.http.get(GRAPH_ENDPOINT)
    .subscribe({
      next: (profile) => {
        this.profile = profile;
      },
      error: (err: AuthError) => {
          this.authService.acquireTokenPopup({
            scopes: this.authService.getScopesForEndpoint(GRAPH_ENDPOINT)
          })
          .then(() => {
            this.http.get(GRAPH_ENDPOINT)
              .toPromise()
              .then(profile => {
                this.profile = profile;
              });
          });
      }
    });

Note, if you want to use acquireTokenRedirect or loginRedirect instead, your application will need to implement handleRedirectCallback separately, not inside the interceptor or where you make the http request. Instead, it needs to be invoked on page load, as demonstrated in the Angular 8 sample. Remember, if you call redirect, the browser will fully redirect away from your application and lose all context, which is why we recommend acquireTokenPopup instead for this scenario.

Does anyone has working link for the angular 8 example?

alvipeo commented 4 years ago

I ended up changing to another library and it works like a charm.

lukan95 commented 4 years ago

I try @anth-git approach. If i run it on localhost, it works fine. But if i try run it on a production server, only a blank page will be displayed.

Im using also hash strategy and msalguards.

Is there a need for a refresh or something else?

ghost commented 4 years ago

I try @anth-git approach. If i run it on localhost, it works fine. But if i try run it on a production server, only a blank page will be displayed.

Im using also hash strategy and msalguards.

Is there a need for a refresh or something else?

This was our exact scenario as well. Hash routing, works locally, does not work in Azure Static Web. I tacked it again last night and was able to resolve/workaround our issues. Some things to note that I was doing that differed from the example code:

Using the example here (without deviation) https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples/msal-angular-samples/angular9-sample-app, I was able to resolve this error, albeit deviating from our original project spec.

Looking at my solution, compared to others, this error appears to throw on wide variety of problems, likely due to a knowledge issue with how MSAL works (there appear to be a lot of gotcha's with this library). If anyone is interested, I'm willing to share our previous source code that produced this error, but tbh, I probably messed something up due to a knowledge gap.

anth-git commented 4 years ago

@ZacharyHiggins-dbMotion I've learned the hard way that the most important thing when dealing with msal is to not make any redirection or authentication calls when in an iframe. It can be easily checked by comparing window !== window.parent.

Apart from that, why did you have to redirect manually? It should be redirected to redirectUri address (the url in the Azure must match with msal config) Are you sure you had it correctly set in a production?

To force login at every page load, you need to pass { prompt: 'select_account' } to authentication methods (e.g. loginRedirect({ prompt: 'select_account' }))

ghost commented 4 years ago

@ZacharyHiggins-dbMotion I've learned the hard way that the most important thing when dealing with msal is to not make any redirection or authentication calls when in an iframe. It can be easily checked by comparing window !== window.parent.

Apart from that, why did you have to redirect manually? It should be redirected to redirectUri address (the url in the Azure must match with msal config) Are you sure you had it correctly set in a production?

To force login at every page load, you need to pass { prompt: 'select_account' } to authentication methods (e.g. loginRedirect({ prompt: 'select_account' }))

Hi @anth-git , thanks for the follow up. Regarding the redirect manually question. I had my redirectURI set to a page that activated MSALGuard, but received a warning that said this was a bad idea. I'm positive I had the redirectURI's matching in prod, mainly because that issue would throw a more specific error at me when I forgot to change it in the config or Azure, and it's still working. It's worth noting that I did change it back to /#/main in our working tree, and I did not get the warning... so this was likely also me misinterpreting the problem due to our deviations coupled with the fact this is our first web project (attempting to modernize an application).

Thanks for the additional info with login redirect. I will give that a shot. If I have any issues, I'll do some research and open a separate issue if needed. Poor @jasonnutter is probably going crazy with this one.

anth-git commented 4 years ago

@ZacharyHiggins-dbMotion I'm not sure if understand what does it mean that you had redirectURI set to a page that activated MSALGuard .

In the dev you should just have it set to http://localhost:4200. In the prod it should be set to the prod address.

Any redirections after app was loaded should be handled using Angular router: { path: '', redirectTo: 'main', pathMatch: 'full', canActivate: [MsalGuard] },

In a simple scenario you don't even need to invoke any Msal methods manually, but can just let the MsalGuard to handle authentication (in such case you also need to pass { prompt: 'select_account' } to MsalAngularConfiguration.extraQueryParameters config, if you want to force login on every page reload)