react-aad icon indicating copy to clipboard operation
react-aad copied to clipboard

ClientAuthError: Token calls are blocked in hidden iframes

Open Nexith opened this issue 5 years ago • 25 comments

Library versions

  • react-aad-msal: 2.3.1
  • msal:1.2.0

Describe the bug Sometimes the following error is displayed in Chrome Developer tools console. index.js:1406 [ERROR] ClientAuthError: Token calls are blocked in hidden iframe It appears 500+ times and then MsalAuthProvider.js:75 Uncaught (in promise) RangeError: Maximum call stack size exceeded Usually happens after a page reload or when coming back after a while of inactivity on the page.

The react application is not rendered in an iframe, it's a default create-react-app. However everything still seems to work fine after it happens.

Expected behavior No error messages in the console.

Desktop (please complete the following information):

  • OS: Windows 10
  • Browser: Chrome
  • Version: 79.0.3945.117 (Official Build) (64-bit)

Nexith avatar Jan 15 '20 09:01 Nexith

We also are seeing this behavior in react-aad-msal 2.3.2

BdN3504 avatar Jan 15 '20 16:01 BdN3504

+1 for

"msal": "1.2.0", "react": "^16.9.0", "react-aad-msal": "2.3.2",

ph-teven avatar Jan 15 '20 17:01 ph-teven

Same here

"msal": "1.2.0", "react": "^16.12.0", "react-aad-msal": "2.3.1",

simonpal avatar Jan 16 '20 07:01 simonpal

Error is being caught here: https://github.com/syncweek-react-aad/react-aad/blob/8db0b8cd9293f1667b71ef9351de22bb2cd41ac1/packages/react-aad-msal/src/MsalAuthProvider.ts#L152

nhance avatar Jan 18 '20 02:01 nhance

Saw this thread in msal https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/1156

tomalex0 avatar Jan 20 '20 19:01 tomalex0

I reverted back to older versions until the problem went away. This is what is working for me now.

"msal": "1.1.3", "react": "^16.12.0", "react-aad-msal": "1.1.3"

iquitwow avatar Jan 20 '20 21:01 iquitwow

Took me a while, but through process of elimination worked out that you need auth.html

in the public folder.

Error Message:

Without it: Screen Shot 2020-01-22 at 3 23 08 PM

Fix:

Add this to your public folder: Screen Shot 2020-01-22 at 3 20 45 PM

Package.json:

  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.3.2",
    "@testing-library/user-event": "^7.1.2",
    "axios": "^0.19.1",
    "msal": "^1.2.1",
    "react": "^16.12.0",
    "react-aad-msal": "^2.3.2",
    "react-dom": "^16.12.0",
    "react-redux": "^7.1.3",
    "react-scripts": "3.3.0",
    "redux": "^4.0.5",
    "typescript": "^3.7.5"
  },

PathToLife avatar Jan 22 '20 02:01 PathToLife

Nexith, in your case, you're probably have some sort of infinite authProvider.somthing() loop because it is inside an <AzureAD>authProvider.somthing()</AzureAD> Provider.

so it's like this

  • <AzureAD> load
  • authProvider.somthing()
  • <AzureAD> detect change, que load
  • <AzureAD> load
  • authProvider.somthing()
  • <AzureAD> detect change, que load
  • <AzureAD> load
  • ...

And example that is most likely related to yours is:

getAccessToken() is called inside <AzureAD> without an check to see if it has already been run. Therefore causing an infinite refresh loop.

Solution:

<AzureAD provider={authProvider}>
                                {
                                    (adProps: IAzureADFunctionProps) => {
                                        const isAuthenticated = adProps.authenticationState === AuthenticationState.Authenticated;
                                        const isUnauthenticated = adProps.authenticationState === AuthenticationState.Unauthenticated;

                                        if (adProps.error) {
                                            console.log(adProps.error.errorMessage);
                                        }

                                        if (isAuthenticated && adProps.accountInfo) {
                                            if (adProps.accountInfo && adProps.accountInfo.jwtAccessToken === undefined && getAccessTokenCallback == null) {
                                                getAccessTokenCallback = authProvider.getAccessToken().then(() => {
someGlobalVariable Set();
getAccessTokenCallback  == null;
 }
)
                                            }

You'll need to probably store getAccessTokenCallback in some sort of global env.. setState usually forces an refresh too. I have an AuthRequestStore class that handles all of this refresh stuff.

See the sample js react app for an implementation that seems to have no errors.

PathToLife avatar Jan 22 '20 05:01 PathToLife

Took me a while, but through process of elimination worked out that you need auth.html in the public folder.

I tried this and updated to msal 1.2.1 and react-aad-msal 2.3.2 adding in the option 'tokenRefreshUri'

const options = {
    loginType: LoginType.Redirect,
    tokenRefreshUri: window.location.origin + '/auth.html',
}

and now I get the following error instead: Unsafe JavaScript attempt to initiate navigation for frame with origin 'https://localhost:3000' from frame with URL 'https://*'. The frame attempting navigation of the top-level window is sandboxed, but the flag of 'allow-top-navigation' or 'allow-top-navigation-by-user-activation' is not set.

We do however call getAccessToken within the <AzureAD> , shouldn't that be handle with with the cache config set automatically and check if it's already been run?

const config = {
    cache: {
        cacheLocation: "localStorage",
        storeAuthStateInCookie: true
    }
}

https://github.com/syncweek-react-aad/react-aad#refreshing-access-tokens

Nexith avatar Jan 24 '20 12:01 Nexith

Took me a while, but through process of elimination worked out that you need auth.html

in the public folder.

Error Message:

Without it: Screen Shot 2020-01-22 at 3 23 08 PM

Fix:

Add this to your public folder: Screen Shot 2020-01-22 at 3 20 45 PM

Package.json:

  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.3.2",
    "@testing-library/user-event": "^7.1.2",
    "axios": "^0.19.1",
    "msal": "^1.2.1",
    "react": "^16.12.0",
    "react-aad-msal": "^2.3.2",
    "react-dom": "^16.12.0",
    "react-redux": "^7.1.3",
    "react-scripts": "3.3.0",
    "redux": "^4.0.5",
    "typescript": "^3.7.5"
  },

Using this approach works for us. Thank you!

BdN3504 avatar Jan 29 '20 11:01 BdN3504

At first I was experiencing the same issue but then I implemented @PathToLife solution and the problem seemed to go away, but I still have issues. Problem is that I still get this error ClientAuthError: Token calls are blocked in hidden iframe sometimes. I notice that when the token expires and I need to fetch a new token, I will actually get a CORS exception and I will have to refresh the page a few times before the errors go away. Here's how I'm experiencing the issue:

I've written a function inside my authProvider file that takes a partially applied (via lodash) axios request, completes it with the rawIdToken in the header, and returns the result.

export const makeRequestWithToken = async (request) => {
    try {
      const token = await authProvider.acquireTokenSilent(authenticationParameters);
      const res = await request({headers: { 'Authorization': `Bearer ${token.idToken.rawIdToken}`}});
      return res;
    }
    catch (error) {
      console.log(error);
    }
}

Preliminary thoughts are that @PathToLife is correct and the problem is related to aquireTokenSilent triggering <AzureAD> to load and the loop occurring. He suggests putting it into the global env but I'm a bit confused. ~~Do we store the token in the env? If so, I'll still have the issue of having to refresh an expired token. Can someone help me spell it out for me? ~~ So I think what this means is to put some kind of flag in the global env to let the app know not to call the the acquireTokenSilent function again.

rosenjcb avatar Feb 03 '20 17:02 rosenjcb

and now I get the following error instead: Unsafe JavaScript attempt to initiate navigation for frame with origin 'https://localhost:3000' from frame with URL 'https://*'. The frame attempting navigation of the top-level window is sandboxed, but the flag of 'allow-top-navigation' or 'allow-top-navigation-by-user-activation' is not set.

Can you share the full text from this error? I know it's unclear, but MSAL is trying to redirect to an error page that usually references some issue in your configuration. If you navigate to the second URL it should give you a more descriptive error.

AndrewCraswell avatar Feb 04 '20 19:02 AndrewCraswell

@rosenjcb, It's a very useful clue that you're still seeing intermittent issues. I just pushed a release with a fix which resolves an issue where an incorrect redirectUri was sometimes being used due to some bad reference issues. I suspect this could have been causing you to hit some edge cases that results in this error, even though you're correctly using the refreshTokenUri and the blank auth.html file.

AndrewCraswell avatar Feb 04 '20 19:02 AndrewCraswell

and now I get the following error instead: Unsafe JavaScript attempt to initiate navigation for frame with origin 'https://localhost:3000' from frame with URL 'https://*'. The frame attempting navigation of the top-level window is sandboxed, but the flag of 'allow-top-navigation' or 'allow-top-navigation-by-user-activation' is not set.

Can you share the full text from this error? I know it's unclear, but MSAL is trying to redirect to an error page that usually references some issue in your configuration. If you navigate to the second URL it should give you a more descriptive error.

There was a missing port number for localhost in the configuration for the Redirect URIs that seemed that have caused the error. Now running [email protected] and [email protected] with the auth.html file and tokenRefreshUri option pointing towards it, everything works fine.

EDIT: Also needed to add https://localhost:3000/auth.html as a Redirect URI.

Nexith avatar Feb 05 '20 11:02 Nexith

So with @AndrewCraswell update of 2.3.3 I have experienced a significant drop in errors. The only point in which I see any ClientAuthError or CORS Exception is when a login is in progress (this basically only happens when a token expires and it attempts to fetch a new one). This is what my stacktrace looks like:

XHRClient.ts:42 GET https://login.microsoftonline.com/mycompany.onmicrosoft.com/v2.0/.well-known/openid-configuration net::ERR_FAILED
(index):1 Access to XMLHttpRequest at 'https://login.microsoftonline.com/mycompany.onmicrosoft.com/v2.0/.well-known/openid-configuration' from origin 'https://my-app.azurewebsites.net' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Logger.ts:19 [ERROR] ClientAuthError: Error: could not resolve endpoints. Please check network and try again. Details: function toString() { [native code] }
XHRClient.ts:42 GET https://login.microsoftonline.com/mycompany.onmicrosoft.com/v2.0/.well-known/openid-configuration net::ERR_FAILED

The work around behind this is to render a page saying during the InProgress state which basically states "Hey, I'm logging you on right now. Try refreshing in a minute" but this obviously isn't ideal. I was hoping it'd just redirect the user to the app once it exits that InProgress state and the user is authenticated.

rosenjcb avatar Feb 05 '20 20:02 rosenjcb

I've experienced the same errors as those mentioned above and added everything mentioned as required to get rid of the errors. Now I'm with the following versions: "react-aad-msal": "^2.3.3" and "msal": "^1.2.1". The only error that I'm receiving is during the Login process: "[ERROR] ClientAuthError: Token calls are blocked in hidden iframes". However, it logs in but it's still annoying to have the error message appearing in the console.

KonstantinDinev avatar Feb 06 '20 13:02 KonstantinDinev

If my log helps in anyways to understand the issue, versions: "react-aad-msal": "2.3.3" and "msal": "1.2.1". tokenBlock

jhaankit avatar Feb 13 '20 05:02 jhaankit

Not sure if this helps. https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/1194

cicorias avatar Feb 13 '20 14:02 cicorias

I was seeing the same error. I was getting this error in the iFrame.
AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application:

Turns out the redirectUrl being sent in the iFrame included /auth.html. I added http://{domain}.com/auth.html to the list of accepted redirectURLs in App registration on Azure.

maike9 avatar Mar 02 '20 18:03 maike9

Another issue when we exclude the

"[ERROR] ClientAuthError: Tokew calls are blocked in hidden iframes"

is when I set the auth options field tokenRefreshUri: window.location.origin + '/auth.html' . Then the error is the following:

The frame attempting navigation of the top-level window is sandboxed, but the flag of 'allow-top-navigation' or 'allow-top-navigation-by-user-activation' is not set.

KonstantinDinev avatar Mar 05 '20 13:03 KonstantinDinev

@KonstantinDinev Can you post the full text of the error, including the URLs? The error you're seeing is saying that there was an error, and AAD was trying to redirect to the error page but MSAL prevented it. If you click on the second URL it will take you to the error page so you can get the error description. Usually this is caused by a configuration issue.

AndrewCraswell avatar Mar 05 '20 16:03 AndrewCraswell

@AndrewCraswell this is the screenshot of the error message. It is very similar to @Nexith 's problem but if I set the redirecUri to auth.html as he does, it returns a blank page without redirecting to the right page.

Screenshot 2020-03-09 at 11 10 55

KonstantinDinev avatar Mar 09 '20 09:03 KonstantinDinev

@KonstantinDinev, I've added both https://localhost:3000 and https://localhost:3000/auth.html to the Azure Auth config for allowed callback urls.

Currently my application config looks like this for authProvider.js:

import { MsalAuthProvider, LoginType } from 'react-aad-msal';
const config = {
    auth: {
        authority: 'https://login.microsoftonline.com/**************',
        clientId: '**************',
        redirectUri: 'https://' + window.location.host
    },
    cache: {
        cacheLocation: "localStorage",
        storeAuthStateInCookie: true
    }
};
const authenticationParameters = {
    scopes: [
        '**************'
    ]
};
const options = {
    loginType: LoginType.Redirect,
    tokenRefreshUri: window.location.origin + "/auth.html"
};
export const authProvider = new MsalAuthProvider(config, authenticationParameters, options);

Still seeing intermittent errors where it get's blocked in the iFrame where you have to stop the page and then click refresh for it to login, but it happens very rarely and only in our dev/prod environment. We are still in development and testing phase so it's not an issue for us at the moment.

Nexith avatar Mar 09 '20 09:03 Nexith

@Nexith thank you very much! I had a configuration issue for the local environment as what @AndrewCraswell pointed earlier. I was targeting http instead of https for the redirect. However, I had to start the project with HTTPS=true npm start and it worked with /auth.html included but the console was still firing [ERROR] ClientAuthError: Token calls are blocked in hidden iframes plus err_cert_invalid.

My work around for Production eliminated the issues. I will post my code:

authProvider.js

import { MsalAuthProvider, LoginType } from 'react-aad-msal';
import Constants from '../api/constants';

const config = {
  auth: {
    authority:   Constants.AZURE_AD.authority,
    clientId:    Constants.AZURE_AD.clientId,
    redirectUri: Constants.SYSTEM.frontend,
    postLogoutRedirectUri: Constants.SYSTEM.frontend,
  },
  cache: {
    cacheLocation: "localStorage",
    storeAuthStateInCookie: true
  }
};

const options = {
  loginType: LoginType.Redirect,
  tokenRefreshUri: Constants.AZURE_AD.refreshTokenUri
};

const authenticationParameters = {
  scopes: [***********]
};

export const authProvider = new MsalAuthProvider(config, authenticationParameters, options);

Constants.js

const { selectedServer, selectedClientId } = config;
const isDevelopment = (process.env.NODE_ENV === 'development');

const FRONTEND_ENV = (isDevelopment) ? 'http://localhost:3000' :
  `https://${selectedServer}/`;

const postReditect = (isDevelopment)   ?
  'http%3A%2F%2Flocalhost%3A3000'  :
  `https%3A%2F%2F${selectedServer}`;

const Constants = {
  AZURE_AD: {
     url:      'https://login.microsoftonline.com',
     tenant:   '******************************',
     clientId: `${selectedClientId}`,
     redirect: `?post_logout_redirect_uri=${postReditect}`,
     get authority() {
       return `${this.url}/${this.tenant}`
     },
     get logout() {
       return `${this.url}/${this.tenant}/oauth2/logout${this.redirect}`
     },
     get refreshTokenUri() {
       return (isDevelopment) ? FRONTEND_ENV : `${FRONTEND_ENV}/auth.html`
     }
  }
};

This works great with no console errors for Production. I decided to leave it using http for local development without the /auth.html in tokenRefreshUri. For local development it still shows [ERROR] ClientAuthError: Token calls are blocked in hidden iframes but it works.

Thanks!

KonstantinDinev avatar Mar 09 '20 13:03 KonstantinDinev

Did as @Nexith on this, adding http://localhost:3000/auth.html to the Redirect URI for the app registration resolved the issue.

chixcancode avatar Mar 18 '20 05:03 chixcancode