Skip to main content

Authentication Sessions and Silent Authentication

The article reviews the technical implementation of Authress authentication and session management. It focuses on:

info

This article picks up where Configuring an identity provider leaves off. If you haven't configured an Application and the Browser SDK to log users in, we recommend using the Authentication Quick Start Guide to get up to speed.

User loginโ€‹

Your user is logged in. They've accomplished when you routed them to their identity provider of choice. This all happens automatically through Authress, and the necessary configuration would have been:

  • Built and deployed an application in your language of choice, possible through one of the Authress Starter Kits.
  • Configured a list of identity providers using OAuth2, SAML, or another integration via an Authress Connection.

In your application, it waited for the user to click Log in and then made a call to the Authress Identity API creating a new authentication request. This is done using the Front end javascript SDK. For a reminder, this code for this is:

Start the user login process
import { LoginClient } from '@authress/login';

// What is my applicationId => https://authress.io/app/#/manage?focus=applications
// What is my authressLoginHostUrl? => https://authress.io/app/#/setup?focus=domain
const loginClient = new LoginClient({ authressLoginHostUrl: 'https://login.application.com', applicationId: 'YOUR_APPLICATION_ID' });
const isUserLoggedIn = await loginClient.userSessionExists();
if (!isUserLoggedIn) {
await loginClient.authenticate({ connectionId: 'SELECTED_CONNECTION_ID', redirectUrl: window.location.href });
}

When this happens Authress, will begin the authentication process. This includes storing some information about where the request came from and who the user could be. They'll be met with a familiar screen asking them to select which provider they want to use, or if the user has logged in before, recommending them one in particular.

Last Used Connection

Authress handles the complex authentication exchange with their provider of choice, and the result is, you get back a JWT access token in your UI app in the user's browser. This access token is a base64url encoded string, that can be used to:

The token contains user identifying information along with an expiration date.

After the user logs in, you can get the Authress generated access token by using the Authress browser SDK:

Get a user's JWT access token in your application UI.
const userToken = await loginClient.ensureToken();
danger

The JWT access token from Authress will expire. When it expires it can and should no longer be used for any purpose, and your user must get a new one before making additional API calls.

Session management and silent authenticationโ€‹

The tokens generated by Authress expire. This ensures that tokens accidentally leaked to potential malicious attackers, cannot be used to impersonate your users and steal their data. However, this means that without another option users will have navigate through the login flow every time the token expires.

To avoid this problem, Authress generates a hidden authentication session that is stored with the user. When the authentication process is completed, four cookies (or parameters) are created:

  • authorization - The JWT access access token which can be fetched using the the Authress browser SDK.
  • user - User identifying data that is pulled from the authentication provider selected by the user and additional data stored in Authress.
  • AuthUserId - The user's Authress user ID, saved internally, which can be used for debugging requests in your services.
  • authress-session - A cryptographically secure random value combined with a sequence identifier and a unique device identifier for the device the user is using.

The authress-session cookie is available to the user's browser and consumed by the SDK. When the JWT access token expires the SDK uses the authress-session token to request a new JWT access token. When this happens the response includes:

  • A new JWT access token
  • An updated user identity
  • A new authress-session token

This is known as Silent Authentication, and is used to keep the user logged in while limiting access token lifetimes.

info

The authress-session tokens must not be used directly. They may change over time in subtle ways that could break direct integrations. To handle this, Authress provides SDKs that enable safe interactions with the JWT, the user identity, and the session token.

Handling expiring tokensโ€‹

A common scenario here is that the token will expire while your users are trying to use it to make requests to your services. Since there could be multiple requests in flight when the token expires you probably need a way to prevent fetching a new token repeatedly. The solution to this is the the browser SDK functions:

  • await loginClient.userSessionExists()
  • await loginClient.ensureToken()

These automatically serialize every request to Authress authentication to ensure that your UI only executes a single refresh. Specifically, these calls create an infinite promise chain, that serializes all requests to get a valid token.

  • When there is a valid token, the Authress SDK validates the existing expiry, and then immediately returns it. This is instantaneous.
  • When there isn't a valid token, the SDK attempts to use the authentication session to perform the silent authentication and get a new token.

During this time all requests to get a valid token are blocked. It also means that there will never be two requests to Authress both attempting to get a new JWT access token at the same time. When the token is valid, all the next requests will automatically proceed.

This is actually important because browsers will pause execution for an extended time when the user is away from the tab/browser. This means things like a "background refresh" using setTimeout or setInterval don't work.

This allows your users to use your UI for hours on end, without ever getting blocked or needing to log in during a critical operation. However, there will be that blip of ~100ms to get a new token whenever it expires. To circumvent this the Authress Browser SDK also schedules a background refresh to enable getting a new token.

Considered Alternativesโ€‹

There are some common alternative strategies that might be tempted to use, but each of them comes with significant draw backs.

  1. Using the background refresh with setTimeout only. As mentioned above setTimeout will not definitely fire before the token expires. You will always have to deal with an expired token state. Depending on setTimeout to make sure you have a valid token, will result in errors for your user analytics and issues for your users as well.

  2. Waiting for a 401 to be returned when calling a service. This seems like a good idea, but it too has issues. A simple mistake in a service accidentally returning a 401 will now cause your user through their browser to infinitely request new tokens. Since every valid token returns a 401, you can't tell the difference between an invalid token and a valid one. Never trust a backend service response status code for deciding whether or not to get a new token. The one exception to this is a primary backend service. In general this should be the Authress Authentication service, but it could also be some Backend For your Frontend. You will still get infinite login loops when there is a problem, but because of the direct coupling between these two services it should be clearer and more expected when it happens.

Requests to your services must block on getting a new access token.

It worth treating 401s as if they are a 403 or another undocumented 4XX status code. When you get one, something is wrong in production. The list of possible problems are:

  • Authress is incorrectly returning already invalid tokens
  • Authress is incorrectly returning tokens signed with the wrong signer
  • The browser UI client has cached an expired token rather than using the Authress SDK
  • The service the browser is calling is incorrectly processing the token
    • It isn't verifying it correctly, there is a bug in the service
    • It has cached an expired Authress public key, and needs to fetch a new one
  • The service is returning a 401 when it should be returning another status code (2XX or 4XX)

Your services should still return a 401, when the JWT Access Token is invalid, but your UI should not assume that it is invalid when it gets back a 401.

In every one of these circumstances, attempting to get a new token is not the optimal solution. There is no caching on the Authress side for tokens, so there is no way to get an expired token without there being a bug. Since one of these services has an actual problem, report and alert on finding this from your client. Your UI might be the first to know there is a problem. Another way to look at this is when you get a 401, either:

  1. The user is logged in, in which case you shouldn't get a 401 something is wrong.
  2. The user isn't logged in, in which case you can already know this by calling the Authress Browser SDK methods to confirm before sending the request to the service.

There is no normal flow that you should ever see a 401. Something is always wrong.

If you do find there is an issue with Authress here, please contact Authress Support immediately.

Multiple devicesโ€‹

When a user logs into multiple devices, each of these devices will get similar JWT access tokens, because the user ID is the same, these access tokens are similar. Of course the expiry of the tokens as well as the token ID (jti) are different, but everything else is the same. On the other hand, each authress-session token will be fundamentally different, because it represents not only the current sequence but also the specific device the session is attached to.

Each device gets its own unique device ID, assigned with the device's current IP Address, JA3 certificate hash, and some additional location information.

In some cases, your product, business, or regulatory conditions might require the number of active sessions a user has to be restricted to just one at a time. Authress supports this by providing a flag that can be set at the application level.

Allow user multi-sessions

If multi-session is disabled, when the user logs in with a new device, the sessions from the previous devices will be replaced, and as such users will only ever have one valid session.

Preventing session hijackingโ€‹

When the current device's access token expires, the device uses the issued authress-session token to fetch new credentials. By doing this, the current authress-session token is invalidated and cannot be used again. The helps prevent cookie theft. These tokens are also not guessable because only a small fix set of them are valid at any one time. In some cases if Authress detects that an previously used authress-session token is attempted to be reused, then whole session will be marked as compromised, and the user will be forced to log in again once the token expires.

Further when the user logs out by calling the Authress log out function, the session is also deleted.

Logging the user out
import { LoginClient } from '@authress/login';

const loginClient = new LoginClient({});
await loginClient.logout();

Interacting with user sessionsโ€‹

For some scenarios, directly interacting with the list of user sessions might provide value to your software application or service. Authress recommends directly exposing this information back to your users in cases where sensitive data might be available. This way your users can use this knowledge to know where they are still logged in, and potentially decide to take action to revoke potentially insecure sessions.

The Authress browser SDK and Authress SDKs provides methods to fetch sessions in the browser and also in your backend services.

The session response data from Authress looks like:

GetUserSessions response
{
"sessions": [
{
"sessionId": "0cd2e882-f0da-45a2-b1de-d03fa8e94784",
"createdTime": "2023-03-27T17:48:02.336Z",
"lastUpdatedTime": "2023-03-27T17:48:02.336Z",
"fingerprint": {
"sourceIp": "8.8.8.8",
"countryCode": "ZH CH",
"city": "Zurich",
"userAgent": "User Agent"
}
}
]
}

And in the Authress management portal, we've chosen to also expose this information:

User Explorer session list

Requests to the user sessions endpoints can also be used to understand how many devices the user is logged into at this moment. This information may be relevant depending on your business case to offer the user different interactions in your service.

Token lifetimes and token invalidationโ€‹

Tokens have fixed lifetimes to prevent accidental compromise due to leakage of old tokens. When the user logs out, the session is deleted, preventing it from being used, even if the authress-session token is stolen.

A common misconception surrounds this concept with the belief that not only the session token but also the JWT token identity need to be revoked. You don't revoke an identity, you revoke the permissions associated the with the user. Their identity won't change, but the permissions will, and therefore token identities don't need to be revoked. Because of this challenge, Authress offers multiple strategies for dealing with issues in this space, and for more information on invalidating sessions see: