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.

Create the Refresh Function

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 is required but either cannot be performed or fails, throw away the invalid stale session. The user is no longer authenticated.

Let's see how this looks in our authentication utility function module.

src/utils/authentication.ts

_15
import { Capacitor } from '@capacitor/core';
_15
import {
_15
Auth0Provider,
_15
AuthConnect,
_15
ProviderOptions,
_15
} from '@ionic-enterprise/auth';
_15
import { getSnapshot, setSession } from './session-store';
_15
_15
// Existing code cut out for clarity. Code not shown here should be left as-is in your project.
_15
_15
const refreshIfExpired = async (): Promise<void> => {
_15
null;
_15
};
_15
_15
export { login, logout, refreshIfExpired, setupAuthConnect };

Add a function that will refresh the auth result if it is expired.

src/utils/authentication.ts

_22
import { Capacitor } from '@capacitor/core';
_22
import {
_22
Auth0Provider,
_22
AuthConnect,
_22
ProviderOptions,
_22
} from '@ionic-enterprise/auth';
_22
import { getSnapshot, setSession } from './session-store';
_22
_22
// Existing code cut out for clarity. Code not shown here should be left as-is in your project.
_22
_22
const refreshIfExpired = async (): Promise<void> => {
_22
const authResult = getSnapshot();
_22
if (
_22
authResult &&
_22
(await AuthConnect.isAccessTokenAvailable(authResult)) &&
_22
(await AuthConnect.isAccessTokenExpired(authResult))
_22
) {
_22
null;
_22
}
_22
};
_22
_22
export { login, logout, refreshIfExpired, setupAuthConnect };

Get the current authResult. If it exists, determine if it needs to be refreshed.

src/utils/authentication.ts

_29
import { Capacitor } from '@capacitor/core';
_29
import {
_29
Auth0Provider,
_29
AuthConnect,
_29
AuthResult,
_29
ProviderOptions,
_29
} from '@ionic-enterprise/auth';
_29
import { getSnapshot, setSession } from './session-store';
_29
_29
// Existing code cut out for clarity. It is left as-is in your code.
_29
_29
const refreshIfExpired = async (): Promise<void> => {
_29
const authResult = getSnapshot();
_29
if (
_29
authResult &&
_29
(await AuthConnect.isAccessTokenAvailable(authResult)) &&
_29
(await AuthConnect.isAccessTokenExpired(authResult))
_29
) {
_29
let newAuthResult: AuthResult | null = null;
_29
try {
_29
newAuthResult = await AuthConnect.refreshSession(provider, authResult);
_29
} catch (err) {
_29
null;
_29
}
_29
setSession(newAuthResult);
_29
}
_29
};
_29
_29
export { login, logout, refreshIfExpired, setupAuthConnect };

If the current auth result is expired, create a new auth result by refreshing the existing one.

src/utils/authentication.ts

_31
import { Capacitor } from '@capacitor/core';
_31
import {
_31
Auth0Provider,
_31
AuthConnect,
_31
AuthResult,
_31
ProviderOptions,
_31
} from '@ionic-enterprise/auth';
_31
import { getSnapshot, setSession } from './session-store';
_31
_31
// Existing code cut out for clarity. It is left as-is in your code.
_31
_31
const refreshIfExpired = async (): Promise<void> => {
_31
const authResult = getSnapshot();
_31
if (
_31
authResult &&
_31
(await AuthConnect.isAccessTokenAvailable(authResult)) &&
_31
(await AuthConnect.isAccessTokenExpired(authResult))
_31
) {
_31
let newAuthResult: AuthResult | null = null;
_31
if (await AuthConnect.isRefreshTokenAvailable(authResult)) {
_31
try {
_31
newAuthResult = await AuthConnect.refreshSession(provider, authResult);
_31
} catch (err) {
_31
null;
_31
}
_31
}
_31
setSession(newAuthResult);
_31
}
_31
};
_31
_31
export { login, logout, refreshIfExpired, setupAuthConnect };

Only attempt the refresh if a refresh token is available. Note that the session will be set to null if it is expired and either there is no refresh token available or the refreshSession() fails.

Add a function that will refresh the auth result if it is expired.

Get the current authResult. If it exists, determine if it needs to be refreshed.

If the current auth result is expired, create a new auth result by refreshing the existing one.

Only attempt the refresh if a refresh token is available. Note that the session will be set to null if it is expired and either there is no refresh token available or the refreshSession() fails.

src/utils/authentication.ts

_15
import { Capacitor } from '@capacitor/core';
_15
import {
_15
Auth0Provider,
_15
AuthConnect,
_15
ProviderOptions,
_15
} from '@ionic-enterprise/auth';
_15
import { getSnapshot, setSession } from './session-store';
_15
_15
// Existing code cut out for clarity. Code not shown here should be left as-is in your project.
_15
_15
const refreshIfExpired = async (): Promise<void> => {
_15
null;
_15
};
_15
_15
export { login, logout, refreshIfExpired, setupAuthConnect };

Call the Refresh

With this logic in place, the next logical question is where to call it. As we will see when we protect-the-routes, it will make sense to perform the refresh as needed prior to appending the access token to outbound requests.

For the example here, however, we will modify our App component to check for a refresh when the application is mounted.

src/App.tsx

_91
import { Redirect, Route } from 'react-router-dom';
_91
import {
_91
IonApp,
_91
IonIcon,
_91
IonLabel,
_91
IonRouterOutlet,
_91
IonTabBar,
_91
IonTabButton,
_91
IonTabs,
_91
setupIonicReact,
_91
} from '@ionic/react';
_91
import { IonReactRouter } from '@ionic/react-router';
_91
import { ellipse, square, triangle } from 'ionicons/icons';
_91
import { AuthenticationProvider } from './providers/AuthenticationProvider';
_91
import AuthActionCompletePage from './pages/AuthActionCompletePage';
_91
import Tab1 from './pages/Tab1';
_91
import Tab2 from './pages/Tab2';
_91
import Tab3 from './pages/Tab3';
_91
_91
import { refreshIfExpired } from './utils/authentication';

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!! 🤓