Offline Support for Authentication

When building a Fusion application with offline support, take care when storing authentication in the browser to enable offline access checking on the client-side.

The localStorage API is an easy way to store data in the browser that also works offline. This article describes how to use it to store authentication.

Storing Authentication for Offline

It is good practice to expire authentication after a time limit. However, the data in localStorage does not expire automatically; you need to have a timestamp in the authentication data object itself. Add the timestamp property to the TypeScript definition for the authentication object, as follows:

interface Authentication {
  timestamp: number;
}

let authentication: Authentication | undefined = undefined;

Also, define the string key for localStorage, as well as the maximum age constant. The examples in this article use a 30-day limit.

const AUTHENTICATION_KEY = 'authentication';
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;

In your login method, if the login is successful, create the object with the timestamp, and save it using localStorage.setItem():

export async function login(username: string, password: string): Promise<LoginResult> {
  const result = await loginImpl(username, password);
  if (!result.error) {
    authentication = {
      timestamp: new Date().getTime(),
    };

    // Save the authentication to local storage
    localStorage.setItem(AUTHENTICATION_KEY, JSON.stringify(authentication));
  }

  return result;
}

The saved authentication can now be reloaded when starting the application offline.

Restoring Authentication on Load

Use the localStorage.getItem() method to restore the authentication when the application starts.

// Get authentication from local storage
const storedAuthenticationJson = localStorage.getItem(AUTHENTICATION_KEY);
if (storedAuthenticationJson !== null) {
  const storedAuthentication = JSON.parse(storedAuthenticationJson) as Authentication;
  // Check that the stored timestamp is not older than 30 days
  const hasRecentAuthenticationTimestamp =
    new Date().getTime() - storedAuthentication.timestamp < THIRTY_DAYS_MS;
  if (hasRecentAuthenticationTimestamp) {
    // Use loaded authentication
    authentication = storedAuthentication;
  } else {
    // Delete expired stored authentication
    setSessionExpired();
  }
}

Remember to check the timestamp before using the loaded authentication.

Removing an Expired Authentication

If the authentication is expired, or when the user logs out, remove the stored authentication from localStorage, as follows:

export function setSessionExpired() {
  authentication = undefined;

  // Delete the authentication from the local storage
  localStorage.removeItem(AUTHENTICATION_KEY);
}

export async function logout() {
  setSessionExpired();
  return await logoutImpl();
}

Complete Example With Offline Support

The following code example contains the complete client-side authentication implementation with offline support:

// Uses the Vaadin provided login an logout helper methods
import { login as loginImpl, LoginResult, logout as logoutImpl } from '@vaadin/flow-frontend';

interface Authentication {
  timestamp: number;
}

let authentication: Authentication | undefined = undefined;

const AUTHENTICATION_KEY = 'authentication';
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;

// Get authentication from local storage
const storedAuthenticationJson = localStorage.getItem(AUTHENTICATION_KEY);
if (storedAuthenticationJson !== null) {
  const storedAuthentication = JSON.parse(storedAuthenticationJson) as Authentication;
  // Check that the stored timestamp is not older than 30 days
  const hasRecentAuthenticationTimestamp =
    new Date().getTime() - storedAuthentication.timestamp < THIRTY_DAYS_MS;
  if (hasRecentAuthenticationTimestamp) {
    // Use loaded authentication
    authentication = storedAuthentication;
  } else {
    // Delete expired stored authentication
    setSessionExpired();
  }
}

export function setSessionExpired() {
  authentication = undefined;

  // Delete the authentication from the local storage
  localStorage.removeItem(AUTHENTICATION_KEY);
}

export async function login(username: string, password: string): Promise<LoginResult> {
  const result = await loginImpl(username, password);
  if (!result.error) {
    authentication = {
      timestamp: new Date().getTime(),
    };

    // Save the authentication to local storage
    localStorage.setItem(AUTHENTICATION_KEY, JSON.stringify(authentication));
  }

  return result;
}

export async function logout() {
  setSessionExpired();
  return await logoutImpl();
}

export function isLoggedIn() {
  return !!authentication;
}