DevDay 2022: Keynotes, workshops, and more. Join us in Sydney, London & Berlin.Register for Free
Best practices

Application Session Management

Let's see how to maintain application sessions in different scenarios

Last Updated On: January 25, 2022

Best practices

Application Session Management

Let's see how to maintain application sessions in different scenarios

Last Updated On: January 25, 2022

Terminology

  • SSO — Single sign-on
  • SLO — Single Logout
  • redirect_uri — The callback URL after login
  • returnTo — The callback URL after logout
  • TLD — Top level domain
  • ITP — Intelligent tracking prevention (by Apple)
  • IdP — Identity provider

Problem Definition

An application can determine the validity of an Auth0 Session via the use of /authorize endpoint. Customers often implement some kind of polling against Auth0 /authorize to determine the session validity and this may not be viable given the potential impact of hitting rate limits, ITP, and third-party cookie issues. This document describes scenarios on how to avoid such issues and implement approaches to maintain app sessions.

Implementation Details

Sessions

A session identifies the user to the app after they have logged in, and is valid for a period of time during which the user can perform a set of interactions within that application. A single session can contain multiple activities (such as page views, events, social interactions, and e-commerce transactions), all of which are stored in the session temporarily while the user is connected.

Session layers:

There are basically three layers of sessions:

  • Application session Though the application uses Auth0 to authenticate users, it may still need to track that the user has logged in to the application. For which, it may have to create a session (for eg., by depending on an access_token expiration).
  • Auth0 session Auth0 also keeps a session for the user and stores their information inside a cookie. The next time a user is redirected to the Auth0 login, the user's information will be inferred.
  • IdP session: This session is involved when Auth0 is federating to another third-party IdP.

In addition to the above session layers, the application also has to be aware of token expirations, especially in OIDC flows.

  • Session vs Token: When a user successfully logs in, a session is created and maintained by Auth0 and indicates that the user has logged in and does not need to re-authenticate for the duration of that session. The session will last until a set expiration time or the user logs out, or the SSO session cookie is deleted from the user’s browser. So if a user leaves an application but later returns and attempts to login before the session expires they will not have to enter their credentials again. On the other hand, tokens are signed information that Auth0 sends back to the client application in a way to securely exchange the user authentication and authorization decisions with the client applications. Access tokens represent authorization and are intended to grant the bearer access to an API either on behalf of a user or an application. Id tokens represent authentication and contain information about the user who authenticated and are intended for the application the user is using.

SSO, SLO & how they work:

Single Sign-on (SSO) occurs when a user logs in to one application and is then signed in to other applications in that agent/browser automatically. The user signs in only one time, hence the name of the feature (Single Sign-on).

Whenever users go to a domain that requires authentication, they are redirected to the centralized authentication domain where they may be asked to log in. If the user is already logged in at the authentication domain, the central authentication server will automatically re-authorize and re-consent(if required) the user’s request to the application and then the user can be immediately redirected to the original domain without signing in again.

Logout in the context of Auth0 implementation is the act of terminating an authenticated session. Auth0 provides tools to help you give users the ability to log out; this includes options for providing different levels of logout and also determining where the user will land after the logout is complete.

  • Application Session Layer Logout: Logging users out of your applications typically results in their application session being cleared, and this should be handled by your application: for the Application Session Layer, there is nothing within your Auth0 tenant that you need to use to facilitate session termination.
  • Auth0 Session Layer Logout: You can log users out of the Auth0 session layer by redirecting them to the Auth0 Logout endpoint so Auth0 can clear the SSO cookie.
  • Identity Provider Session Layer Logout: It is not necessary to log the users out of this session layer, but you may be able to use Auth0 to force the logout, if required, by passing a parameter to the Auth0 Logout endpoint (if supported by third party IDP). This is applicable for SAML scenarios and other scenarios are covered in the next sections.

Maintain Sessions in SPAs and Regular Web Apps

App session with a backend:

Applications with a backend may track sessions with a server-side application session (a server-side cookie). This cookie may track its expiration by depending on the token response from Auth0. If the cookie expires, the backend has a few options to get the token response from Auth0 and then slide the app session.

  • Perform a 302(redirect) to Auth0 /authorize endpoint. Here, if the session is still valid on Auth0, then new tokens are replied back on the callback service without asking for the user to re-login.
  • During initial authentication, request for a refresh token. Later, the backend can use refresh token to get the new tokens when the app session expires. This approach makes your backend a stateful service and can be extended to be a centralized session management service. The session cookie may only store an anchor (an identifier stored in it) and every time the front end makes a call to the backend, the cookie is associated with the request. The backend will get the refresh token(if required) from the storage using the mapped identifier (or anchor) in the app session cookie. However, this solution will be useful only when the front end & backend are tied to the same TLD.
  • Review our Regular web app Quickstarts & SDKs for the implementation details.

App session on SPA.

Use the approach implemented by our SDKs. On load of the application, our SDKs check for a valid refreshtoken (if there was a previous valid rotating refresh_token issued to the SPA). As a fallback mechanism, the SDK does a silentAuthentication (prompt=none call, if no valid refreshtoken exists) and only if all these steps fail, a complete redirect happens to Auth0 to check for SSO sessions.

  • If any of the above validations succeed (refresh_token validation OR SilentAuthentication OR stateful request to Auth0 cookie), then the user is logged into the application automatically. Otherwise, the app can show a public page or Auth0 SDK will redirect the user to Universal login to complete an interactive flow.

Client applications can still perform a prompt=none when there is an active user in that app, and only when the client app is unable to re-hydrate the cache with an existing rotating refreshtoken (for eg., if the rotating refreshtoken is stored in memory, then it is lost on page refresh and therefore, in this case, the fallback is prompt=none call to re-hydrate the app session).

This approach will avoid multiple unnecessary calls to Auth0 and will have a better user experience.

Issues with Maintaining App Session on SPA with prompt=none

Every SPA application, if guarded with the Auth0 SDK, on load makes a prompt=none call to Auth0 except when there is a valid rotating refresh_token in the browser storage. This is required to seamlessly load the app session by checking for a valid Auth0 SSO cookie on that browser.

However, in a few cases, this can lead to rate limits if the client application is continuously polling against Auth0’s /authorize endpoint with prompt=none

For eg.,

  • The client application, to follow best practices, might have kept its local app session short and therefore the client application keeps refreshing its local session by continuously polling for the validity of the Auth0 SSO cookie with prompt=none iframe calls.
  • Poor client app implementation can lead the above polling to happen even when the user remains inactive(ie., in the background).
  • If the user remained inactive beyond the inactivity SSO timeout in Auth0, then Auth0 responds with “login_required”. However, the polling would continue in the background regardless of the error and therefore increase unwarranted traffic to Auth0 and the client application.

Removing polling on client applications is necessary to reduce a significant amount of invalid network calls against Auth0 as well the client app. This is also required to avoid rate limits on Auth0 Authentication API

A Complex Use Case

If you made it this far, I believe you already know the best practices on how to maintain the app sessions. Most of the client implementations are not complex and might be able to establish sessions using Auth0 SDKs as I described in the previous sections. However, it is not always simple and I want to cover a complex use case.

Let’s consider a scenario where there are multiple Single Page Applications hosted on different domains (e.g. travel0.us,travel0.de, travel0.uk and etc.), and Auth0 is tied to a different domain login.travel0.com. The requirement is for users to be able to use Single Sign-On (SSO) and Single Logout (SLO) across all these SPAs, seamlessly.

Issues:

In this use case, Auth0 supports SSO out of the box using Universal Login. However, every SPA application in our above example should do a redirect to Auth0 to be able to load its local session. At times, customers want their users to seamlessly land on a logged-in page for every SPA on that browser if there is a valid session without redirects. This seamless landing on a logged-in page will require an iframe prompt=none call to Auth0 and will not work (when SPA and Auth0 are tied to different top-level domains) in Safari due to ITP and eventually in Chrome due to google’s efforts to phase out the third-party cookies.

On the other side, the Auth0 Logout endpoint does not cover all scenarios where users need to be signed out of all of the applications they used on that browser. Other than when Auth0 is using SAML, Auth0 does not natively support single logout (SLO).

Following are a few solutions that could be implemented to achieve the above scenarios and can be categorized into two patterns: Push and Poll.

Polling based solutions

Polling happens when the client application polls against an endpoint to determine the validity of a session.

1. Poll against Auth0 using prompt=none

A common approach is where all the client applications periodically poll against Auth0 with prompt=none iframe calls to check if the user has initiated a logout request to Auth0 from any one of the apps and if so, perform a local logout.

This is commonly known as silentAuthentication and if the response is login_required from Auth0, the user will be logged out from the app, else the application will receive a new set of tokens using which it can re-establish its local session.

  • This will not work (when SPAs and Auth0 are tied to different top-level domains) in Safari due to ITP and eventually in Chrome due to google’s efforts to phase out the third-party cookies.
  • This also has a major impact on the application and end-users if the Auth0 authentication API rate limits are not considered.

2. Poll against a client-side endpoint

You can consider polling on the client-side to track logout. Set a client-side SLO cookie (eg: slo_flag:true) associated against the SPA domain when the user initiates a logout. Every other app on that browser can poll for that client-side cookie, to verify if there has been a logout issued against Auth0 and then kill its local session.

This approach can also be extended to determine if the login has happened on a different SPA in the same browser, using a different flag ssoflag:true. Only, upon detecting the flag (ssoflag:true), the current application can make a prompt=none call to establish its local session. This will reduce the number of prompt=none calls to Auth0. A similar SSO approach is used by Auth0 SDKs.

  • This approach also has the same impact (when SPAs and Auth0 are tied to different Top-level domains) in Safari due to ITP and eventually in Chrome due to google’s efforts to phase out the third-party cookies.

Redirect and Chain login/logout

To avoid ITP issues when dealing with multiple TLD’s and as an alternate to client-side cookie polling via iframe, you can implement a redirect using a common server-side login/logout endpoint. This common endpoint can be called by Auth0 redirect rules if it is to chain login requests OR the common endpoint can itself be the application callback (the redirect_uri/returnTo that Auth0 redirects the user after successful login/logout) that chains the login/logout session to every other app in that browser.

This is similar to Google/youtube approach, where a common endpoint on google redirects to youtube to set the session on youtube.

  • This could become complex if there are many applications in the ecosystem. The number of redirects will be equal to the number of applications and could lead to user experience issues if one of the redirects fails.
  • This implementation also depends on customer needs and the actual behavior of the browser.
  • Consider race conditions on how the /authorize calls are initiated against Auth0.

Push events

The push approach is when every application on logout, calls a common logout route that pushes logout events to all the apps in the ecosystem. This approach can solve problems like rate limiting, 3rd party cookie restrictions with browsers and therefore give a better UX.

1. Using web-socket

A Web Socket API can be used to push the SLO event across apps that have subscribed to that slo-event and are open within a user-agent.

  1. On logout, all SPA applications can call the common logout URL https://logout.common.com/logout
  2. The common logout URL is a backend service in this example and will push an event to all SPA applications.
/*
*NOTE: This is sample code that references to an open library `pusher` as an example. The customer is free to use any messaging websocket service.
*The code cannot be used in production code without testing & security review. This may need alteration to be production ready
*/
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const Pusher = require("pusher");
const app = express();

const corsOptions = {
    origin: '<CALLING APP URL>'
  };
app.use(cors(corsOptions));

// create application/json parser
let jsonParser = bodyParser.json()
const pusher = new Pusher({
  appId: "YOUR_PUSHER_APP_ID",
  key: "YOUR_PUSHER_APP_KEY",
  secret: "YOUR_PUSHER_APP_SECRET",
  cluster: "YOUR_PUSHER_APP_CLUSTER",
  useTLS: true
});

app.get('/', (request, response) => {
});

//Protect this endpoint with an access_token
app.post('/api/v1/trigger/slo-event', jsonParser, function (req, res) {
    pusher.trigger(req.body.channelName, "slo-event", {
       slo: true
      });
});

app.listen(3002, () => {
  console.log('Server running on localhost:3002');
});
  1. The child SPA will call logout by subscribing to the channel event.
/*
*NOTE: This is sample code that references to an open library `pusher` as an example. The customer is free to use any messaging websocket service.
*The code cannot be used in production code without testing & security review. This may need alteration to be production ready
*/

//..other code on the client SPA

let channelName = '';

async function postData(url = '', data = {}) {    
    const response = await fetch(url, {
      method: 'POST',
      mode: 'cors'
      cache: 'no-cache',
      credentials: 'same-origin',
      headers: {
        'Content-Type': 'application/json'
      },
      redirect: 'follow',
      referrerPolicy: 'no-referrer',
      body: JSON.stringify(data)
    });
    return response.json();
  }  

//SLO event triggered within the app on visiting logout route or clicking a button
const triggerSLO = () => {
  //Invoking below endpoint will send SLO notification to other apps
  postData('<SERVER-URL>/api/v1/trigger/slo-event', { channelName: channelName })
    .then(data => {
      localLogout();
    });
}

const localLogout = () => {
  try {
    console.log("local Logging out");
    auth0.logout({
      returnTo: window.location.origin
    });
  } catch (err) {
    console.log("Log out failed", err);
  }
};

const pusher;
function setupPusher(channelName) {
  pusher = new Pusher(YOUR_PUSHER_APP_KEY, {
    cluster: YOUR_PUSHER_APP_CLUSTER,
    //OTHER_AUTH_PARAMS
  });
  var channel = pusher.subscribe(channelName);
  channel.bind('slo-event', (data) => {
    if (data.slo === true) {
      localLogout();
    }
  })
}

//Get fingerPrint of the user-agent to identify the user logged in applications
  const fpPromise = import('https://openfpcdn.io/fingerprintjs/v3')
                    .then(FingerprintJS => FingerprintJS.load());

  async function getVisitorData() {
    const fp = await import('https://openfpcdn.io/fingerprintjs/v3')
      .then(FingerprintJS => FingerprintJS.load({ token: '<TOKEN>' }));
    return await fp.get({ extendedResult: true });
  }

// Will run when page finishes loading
window.onload = async () => {    
  const { visitorId, incognito} = await getVisitorData();
  if (incognito) {
    channelName = visitorId + '_incognito';    
  } 
  setupPusher(channelName);
};

2.Using iFrame postMessage.

In this approach, every application will call a common logout route which in turn pushes messages to all apps using window.postMessage via iFrame. Each app can subscribe to the message event.

  1. On logout, all applications would use the common logout URL https://logout.common.com/logout?slo
  2. The common logout URL will call each SPAs logout route in iframes. The common logout service can be registered with a list of allowed application logout URLs in the ecosystem or get the list of clients the user authenticated in token from Auth0 rule using context.sso.current_clients.
/*
*NOTE: This is sample code and can not be used in production code without testing and may need alteration to be production ready
*/
if (window.location.search.includes('slo')) {
   var iframe = document.createElement('iframe');
   iframe.src = 'https://child.anotherdomain.com/logout?slo';
   iframe.width = 0;
   iframe.height = 0;
   document.body.appendChild(iframe);
 };
  1. The child SPA will clear its local session and send a message to the parent window
/*
*NOTE: This is sample code and can not be used in production code without testing and may need alteration to be production ready
*/
if (window.location.search.includes('slo')) {
   return logout();
 }
const logout = () => {
 try {
   console.log("local Logging out");
   auth0.logout({
     localOnly: true
   });
   window.parent.postMessage("logout from child.anotherdomain.com", 'https://logout.common.com/logout');
 } catch (err) {


   console.log("Log out failed", err);
 }
};
  1. On successfully receiving a message from the child frame, the common logout URL will call Auth0 logout and finally return to the calling application.
/*
*NOTE: This is sample code and can not be used in production code without testing and may need alteration to be production ready
*/
window.addEventListener("message", (event) => {
 if (event.origin !== "https://child.anotherdomain.com")
   return;
 logout(event.origin);
}, false);


const logout = (targetRedirect) => {
 try {
   auth0.logout({
     returnTo: targetRedirect
   });
 } catch (err) {
   console.log("Log out failed", err);
 }
};
  • In the above example, Auth0 logout is called only after all SPA local sessions are removed. Depending on the use case, the customer can call Auth0 log out first and on logout returnTo, the common logout endpoint can then chain the logout requests to other SPAs via iframes.
  • Consider Race conditions where the logout is in progress and the user tries to login at the same.

Summary

Summary

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon