1
votes

We have a Single Page App (SPA) that uses Azure Active Directory "Easy Auth", e.g., the code-less solution. This seems to work ok when users first open the the application. They are redirected to the Microsoft login page and they can authenticate and then access the application.

Then, because its an SPA, users will navigate around and only fire Ajax requests. The problems come approximately 24 hours later when the session cookie expires. Users likely still have the same browser tab open and do not perform a full page refresh. Then they may be working on a record and at some point their next Ajax PUT request fails with a Redirect HTTP status and they loose their work.

So they key question is:

How can we make SPA Ajax requests extend a current user's session so that their session will not expire when they are actively using the application?

It seems like the Azure AD Easy Auth service does not "honor" activity on the part of the user, which leads us to believe that the session cookie never gets updated.

Note: We've recently done some testing with the /.auth/refresh endpoint and this does not solve the problem either.

2
Can you confirm that the request which was redirected to the login page contains the authentication cookie?Fei Xue - MSFT
@FeiXue, yes, the requests, for example a PUT request contains the a cookie called 'AppServiceAuthSession'. Should we also be sending in a Bearer token with these ajax calls? That was one idea we had to resolve this issue.user1527312

2 Answers

4
votes

There are several ways you can possibly solve this. Here are a few that I can think of:

  1. Use local storage: The problem you mentioned is that user's lose their work due to the redirects. The problem of losing work can be solved if you persist the in-progress state in local storage so that it's available when they are redirected back to the page.
  2. Switch to using tokens: The /.auth/refresh endpoint doesn't refresh the AppServiceAuthSession when using AAD because AAD doesn't support refreshing the user information. What you can do instead is authenticate with your backend using the x-zumo-auth tokens. The /.auth/refresh endpoint will correctly refresh these tokens. If you're explicitly logging in users using /.auth/login/aad, then you can add the session_mode=token as a query string parameter. This is done for you if you use the Mobile Apps JavaScript SDK. If login is automatic, then you'll need to add session_mode=token in the additionalLoginParams setting of your auth config. You can then parse the authentication token from the #token fragment which is added to the URL after the login completes.
  3. Use hidden iframes: I haven't tried this myself, but if you can get it working it might require the least amount of code change. The idea is that you use a hidden iframe to re-login the user periodically when you detect they are active. The iframe would need to point to something like ./auth/login/aad?prompt=none&domain_hint={userdomain.com} where {userdomain.com} is the last part of the user's email address - e.g. contoso.com. These parameters get passed to the AAD login page, and the login should complete automatically without any user interaction. Test it manually a few times in a browser window to make sure it works correctly. The result should be an updated auth cookie with a fresh expiration.

Let me know in the comments if you have any questions or issues with any of these options.

1
votes

Expanding on Chris Gillum's answer with implementation example:

Scenario: Single Page Application (SPA) with Progressive Web App (PWA) capabilities, hosted in Azure Web App. Added authentication using Azure Web Authentication/EasyAuth.

Ran into similar/same issue: Initial loads of the SPA worked fine, but after period of hour(s) (token expires) the app "breaks" - in SPA on iOS tablet that manifested for me with endless whitescreen and seemingly no practical fix (force killing did NOT resolve). Error messages thrown ranged from 401 (understandable) to service-worker refusing to process scripts/handle 302 redirects/etc (less obvious where problem may be).

SPA + Azure Web Authentication/EasyAuth tweaks:

  1. If using MDM, disable "Block Safari navigation menu bar" feature in the MDM for this app. This appears to allow the app to work as expected after force kill (it would reload the page, see expired token, redirect to login and then back to the app). I'm not sure if this behavior is controllable in manifest.json, may be iOS specific capability.

Disable "Block Safari navigation menu bar

  1. Hidden iframe refreshing of token + Timer/check token periodically (in ajax calls, etc):

Note: As of ~2021-04, Chromium based browser worked with hidden iframe method. For other browsers the AAD page would experience errors and fail - current solution suggested would be storing app state -> navigate to AAD login page with redirect param -> User logs in and redirected back to the app -> App state restored w/ refreshed token.

refreshAuthToken() {
  //Chrome based browsers work with silent iFrame based token reAuth
  if (this.browserChromium()) {
    let domainHint = "contoso.com"; //Domain of your organization users (e.g. [email protected])

    //Remove existing iframe (if exists), to minimize history/back button entries
    let existingFrame = document.getElementById("authIFrame");
    if (existingFrame) {
      existingFrame.remove();
    }

    //Inject iFrame that will call endpoint to refresh token/cookie
    console.log("Refreshing auth token (quietly)...");
    let iframe = document.createElement("iframe");
    iframe.id = "authIFrame";
    iframe.style =
      "width: 0; height: 0; border: 0; border: none; position: absolute; visibility: hidden;";
    iframe.src = `/.auth/login/aad?prompt=none&domain_hint=${domainHint}`;
    document.body.appendChild(iframe);

    new Promise(r => setTimeout(r, 2000)).finally(() => resolve()); //Hacky method of "waiting" for iframe to finish
  } else {
    console.log("Refreshing auth token (via page reload)...");
    window.location.replace("/.auth/login/aad?post_login_redirect_url=/?restoreData=true");
  }
},

//
// Timer example:
//
setInterval(() => {this.refreshAuthToken()}, 1000 * 60 * 5); //Fire every 5 minutes

//
// And/or periodically call this function maintain token freshness
//
checkAuthToken() {
  //this.authEnd = JWT from /.auth/me "exp" claim
  let now = new Date() / 1000;
  let expirationWindow = this.authEnd - 600; // Consider token expiring if 10 minutes or less remaining
  if (now >= expirationWindow) {
    console.log("Auth Token expired - Refreshing...")
    this.refreshAuthToken();
  } else {
    // console.log("Auth token still healthy.");
  }
}
  1. Nicety: Enable anonymous access to PWA icons (if possible). iOS requires icons be publicly accessible when saving PWA to homescreen, otherwise uses screenshot of app rather than formal icon: https://stackoverflow.com/a/67116374/7650275