Skip to main content
Version: 6.x

Refresh the Session

Overview

When a user logs in using Auth Connect the application receives an AuthResult that represents the authentication session. The AuthResult object provides access to several types of tokens:

  • ID Token: The ID token contains information pertaining to the identity of the authenticated user. The information within this token is typically consumed by the client application.
  • Access Token: The access token signifies that the user has properly authenticated. This token is typically sent to the application's backend APIs as a bearer token. The token is verified by the API to grant access to the protected resources exposed by the API. Since this token is used in communications with the backend API, a common security practice is to give it a very limited lifetime.
  • Refresh Token: Since access tokens typically have a short lifetime, longer lived refresh tokens are used to extend the length of a user's authentication session by allowing the access tokens to be refreshed.

The key take away is that OIDC authentication servers are typically set up to return a short lived access token along with an much longer lived refresh token. For example, the access token may expire after one hour with the refresh token expiring after five days. This allows the application to use the access token for a period of time and then refresh it after that time has elapsed. As such, the application needs to be able to detect that the access token has expired and request a refresh of the session.

We will build upon the application we created in the getting started tutorial in order to implement a token refresh workflow.

Let's Code

As mentioned previously, this tutorial builds upon the application created when doing the getting started tutorial. If you have the code from when you performed that tutorial, then you are good to go. If you need the code you can make a copy from our GitHub repository.

The refresh strategy used in this tutorial is:

  1. If a session exists, determine if the access token is expired.
  2. If the session token is expired it can be refreshed if a refresh token is available.
  3. If a refresh token is available, refresh the session and replace the existing stale session with the refreshed one. The user's authentication has been made valid again.
  4. If a refresh cannot be performed or the refresh fails, throw away the invalid stale session. The user is no longer authenticated.

Let's see how this looks in our useAuthentication composable.

src/composables/authentication.ts

_10
const getAuthResult = async (): Promise<AuthResult | null> => {
_10
return getSession();
_10
};

We currently have a getAuthResult() function that is a simple pass-through to a function from the session composable.

src/composables/authentication.ts

_10
const getAuthResult = async (): Promise<AuthResult | null> => {
_10
return getSession();
_10
};
_10
_10
const refreshAuthResult = async (
_10
authResult: AuthResult
_10
): Promise<AuthResult | null> => {
_10
return await AuthConnect.refreshSession(provider, authResult);
_10
};

Add a refreshAuthResult() function. For now it can be a simple pass-through to AuthConnect.refreshSession().

src/composables/authentication.ts

_17
const getAuthResult = async (): Promise<AuthResult | null> => {
_17
let authResult = await getSession();
_17
if (
_17
authResult &&
_17
(await AuthConnect.isAccessTokenAvailable(authResult)) &&
_17
(await AuthConnect.isAccessTokenExpired(authResult))
_17
) {
_17
authResult = await refreshAuthResult(authResult);
_17
}
_17
return authResult;
_17
};
_17
_17
const refreshAuthResult = async (
_17
authResult: AuthResult
_17
): Promise<AuthResult | null> => {
_17
return await AuthConnect.refreshSession(provider, authResult);
_17
};

If an AuthResult exists and contains an expired access token, then we need to attempt a refresh.

src/composables/authentication.ts

_21
const getAuthResult = async (): Promise<AuthResult | null> => {
_21
let authResult = await getSession();
_21
if (
_21
authResult &&
_21
(await AuthConnect.isAccessTokenAvailable(authResult)) &&
_21
(await AuthConnect.isAccessTokenExpired(authResult))
_21
) {
_21
authResult = await refreshAuthResult(authResult);
_21
}
_21
return authResult;
_21
};
_21
_21
const refreshAuthResult = async (
_21
authResult: AuthResult
_21
): Promise<AuthResult | null> => {
_21
let newAuthResult: AuthResult | null = null;
_21
if (await AuthConnect.isRefreshTokenAvailable(authResult)) {
_21
newAuthResult = await AuthConnect.refreshSession(provider, authResult);
_21
}
_21
return newAuthResult;
_21
};

We should only refresh the session if a refresh token exists. Otherwise the refresh should return null signifying that we no longer have a valid session.

src/composables/authentication.ts

_25
const getAuthResult = async (): Promise<AuthResult | null> => {
_25
let authResult = await getSession();
_25
if (
_25
authResult &&
_25
(await AuthConnect.isAccessTokenAvailable(authResult)) &&
_25
(await AuthConnect.isAccessTokenExpired(authResult))
_25
) {
_25
authResult = await refreshAuthResult(authResult);
_25
}
_25
return authResult;
_25
};
_25
_25
const refreshAuthResult = async (
_25
authResult: AuthResult
_25
): Promise<AuthResult | null> => {
_25
let newAuthResult: AuthResult | null = null;
_25
if (await AuthConnect.isRefreshTokenAvailable(authResult)) {
_25
try {
_25
newAuthResult = await AuthConnect.refreshSession(provider, authResult);
_25
} catch (err) {
_25
null;
_25
}
_25
}
_25
return newAuthResult;
_25
};

In rare instances, the refresh could fail. For example, if the refresh token has expired or is otherwise invalid. In such cases we should also return null signifying that we no longer have a valid session.

src/composables/authentication.ts

_26
const getAuthResult = async (): Promise<AuthResult | null> => {
_26
let authResult = await getSession();
_26
if (
_26
authResult &&
_26
(await AuthConnect.isAccessTokenAvailable(authResult)) &&
_26
(await AuthConnect.isAccessTokenExpired(authResult))
_26
) {
_26
authResult = await refreshAuthResult(authResult);
_26
}
_26
return authResult;
_26
};
_26
_26
const refreshAuthResult = async (
_26
authResult: AuthResult
_26
): Promise<AuthResult | null> => {
_26
let newAuthResult: AuthResult | null = null;
_26
if (await AuthConnect.isRefreshTokenAvailable(authResult)) {
_26
try {
_26
newAuthResult = await AuthConnect.refreshSession(provider, authResult);
_26
} catch (err) {
_26
null;
_26
}
_26
}
_26
await saveAuthResult(newAuthResult);
_26
return newAuthResult;
_26
};

Save the refreshed value. If newAuthResult is null, indicating that the refresh failed, the session will be cleared.

We currently have a getAuthResult() function that is a simple pass-through to a function from the session composable.

Add a refreshAuthResult() function. For now it can be a simple pass-through to AuthConnect.refreshSession().

If an AuthResult exists and contains an expired access token, then we need to attempt a refresh.

We should only refresh the session if a refresh token exists. Otherwise the refresh should return null signifying that we no longer have a valid session.

In rare instances, the refresh could fail. For example, if the refresh token has expired or is otherwise invalid. In such cases we should also return null signifying that we no longer have a valid session.

Save the refreshed value. If newAuthResult is null, indicating that the refresh failed, the session will be cleared.

src/composables/authentication.ts

_10
const getAuthResult = async (): Promise<AuthResult | null> => {
_10
return getSession();
_10
};

Next Steps

This code represents the basic flow needed for an Auth Connect session refresh. It can easily be expanded to support more advanced scenarios.

For example, let's say that rather than only refreshing expired sessions our application would like to also refresh sessions that are about to expire, let's say within the next five seconds. Rather than using AuthConnect.isAccessTokenExpired(), your application could use a combination of receivedAt and expiresIn to calculate when to access token is due to expire and use the current time to detect if the access token has either expired or is about to expire shortly. The code can then preemptively perform the refresh in those cases.

We suggesting starting with the flow documented here and modifying it as needed to fit your requirements.

Happy coding!! 🤓