kurifodo kurifodo - 3 years ago 166
Javascript Question

Infinite login loop when using adal.js 1.0.14

I'm encountering an infinite login loop with adal.js 1.0.14. I'm NOT using angular, but my application is a SPA. The issue occurs in Chrome and Edge.

Repro steps




  1. Visit my SPA in Incognito Mode to ensure clean state

  2. Create authentication context and invoke handleWindowCallback()

  3. Application prompts me to login since
    getCachedToken(myClientId)
    is null

  4. Redirected to AAD login page

  5. Redirected back to site after successful login

  6. Create authentication context and invoke
    handleWindowCallback()

  7. Logged in successfully, all is well...

  8. Wait for the
    id_token
    to expire (i was simulating this by setting the time to 0 in localStorage)

  9. Refresh the SPA via browser's refresh button

  10. Create authentication context and invoke
    handleWindowCallback()

  11. Application prompts me to login since
    getCachedToken(myClientId)
    is null

  12. Redirected to AAD login page

  13. Redirected back to site after successful login

  14. Steps 10-13 repeat infinitely from here on



With the debugger stepping through
handleWindowCallback()
of step 10, I see adal.login.error gets set to "
Nonce is not same as undefined
" within
saveTokenFromHash
of adal.js code.

adal.js verbose logging



Here's what is present in the console at step 11:

authenticator.js:26 Fri, 21 Jul 2017 05:37:50 GMT:1.0.14-VERBOSE: State: 84c5d552-3f24-4f7f-a871-71e67f8d6482
authenticator.js:26 Fri, 21 Jul 2017 05:37:50 GMT:1.0.14-INFO: Returned from redirect url
authenticator.js:26 Fri, 21 Jul 2017 05:37:51 GMT:1.0.14-INFO: State status:true; Request type:LOGIN
authenticator.js:26 Fri, 21 Jul 2017 05:37:51 GMT:1.0.14-INFO: State is right
authenticator.js:26 Fri, 21 Jul 2017 05:37:51 GMT:1.0.14-INFO: Fragment has id token


adal configuration



{
cacheLocation: 'localStorage',
clientId: 'my applicationId goes in here...',
instance: 'https://login.microsoftonline.com/',
postLogoutRedirectUri: window.location.origin,
tenant: 'microsoft.onmicrosoft.com'
}


Code



This code is executed upfront when loading my SPA (repro steps 1-3 and 9-11 in other words).

var authenticationContext = new AuthenticationContext(configuration);

authenticationContext.handleWindowCallback();

if (!authenticationContext.getCachedToken(authenticationContext.config.clientId)) {
authenticationContext.login();
}


What have I tried?




  • I'd like to avoid clearing the cache before invoking login. As I understand it, this will work around the issue, but is not desirable.

  • This GitHub issue suggested adding
    anonymousEndpoints = []
    to adal config, but this did not help.



Let me know if more details are needed. Thanks in advance.

Answer Source

After more debugging, I have resolved my specific issue.

Due to the nature of Javascript, code after authenticationContext.login() is executed. Calling login() will navigate the application to AAD sign-in page, but until navigation occurs, my code was still executing unexpectedly. I incorrectly assumed once login() was invoked, my code was done running and it was in ADAL's hands.

My unconfirmed hunch is that state was being clobbered by calling acquireToken(authenticationContext.config.clientId) immediately after login(), which manifested in Nonce is not same as undefined to appear as the error.

I say unconfirmed because I wasn't able debug into what exact state caused the issue. However, I changed my code path to not invoke acquireToken immediately after login() and the issue no longer occurred.

Fei's answer helped me isolate my issue, so thank you! In the spirit of sharing my resulting, functional code for the benefit of others, I adapted Fei's sample. Essentially, I wanted to renew tokens after they expire, so I setup an interval to invoke acquireToken every 20 seconds, but not immediately after application startup (when login() is likely to occur).

<html>
<head>
    <script src="js\adal.js"></script>
</head>
<body>
<script>
    var configuration = {
        cacheLocation: 'localStorage',
        clientId: 'my client id',
        instance: 'https://login.microsoftonline.com/',
        postLogoutRedirectUri: window.location.origin,
        tenant: 'mytenant.onmicrosoft.com'
    }

    var authenticationContext = new AuthenticationContext(configuration);

    authenticationContext.handleWindowCallback();

    if (!authenticationContext.getCachedToken(authenticationContext.config.clientId)) {
        authenticationContext.login();
    }

    setInterval(function () {
        authenticationContext.acquireToken(authenticationContext.config.clientId, function (errorDescription, token, error) {
            // do callback stuff...
        });
    }, 20000);

    function login() {
        authenticationContext.login();
    }
    </script>

    <div>
        <button id="button1" onclick="login()">Login</button>
    </div>
</body>
</html>
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download