Blog

Using Netlify Identity in a Vue SPA without localStorage

The Netlify Identity service can make implementing authentication functionality a whole lot easier. Using the Netlify Identity Widget increases the convenience. But there's the matter of using them in a secure way especially when you are trying to secure single page apps (SPAs).

For Vue, an example exists that Netlify links to in the examples folder of the Netlify Identity Widget Github repository which shows how to incorporate authentication into a simple Vue app via Netlify Identity and the widget. The example app is written to store the logged in user's information (which includes the auth token) in Vuex's Store after a successful login. However, the user state is set by the following in store/modules/user.js:

window.localStorage.getItem('user')

This is done for persistence reasons so that the app keeps the user's logged in state and information thus preventing the need for the user to log back in again if they reload or close the page. The problem with using localStorage to set the user in state for authentication is that this is dependent on the client which can use JavaScript to set this to anything. For instance, go to the frontend Vue Example app, open up the brower's Console, and enter:

window.localStorage.setItem('user', true);

then refresh the page. The page will refresh as an authenticated user showing the message "Hello {blank}", a Log Out where the Log In and Sign Up buttons were, and you can now click on the Protected Page link to show a protected message without the manifestation of an unauthenticated popup.

I also illustrate this using a sample Nuxt.js app hosted here (source). The Circumvent button merely executes the same localStorage function along with a page refresh afterwards.

What can be done for a serverless SPA?

I wrestled with this for a while since there are several options in Nuxt: Auth Module, Auth External API (JWT), but they seem to all involve a backend or a deeper delve into Netlify Identity's API via GoTrue. There are debates on whether to store authentication information in localStorage, and there are several other tutorials out there that teach storing auth tokens in much the same way the Vue example does.

The answer may lie in an article by Auth0. Their contention is that you shouldn't store tokens in local storage. They proffer some alternatives depending on your app's situation, but the last part of the article applied to me.

If you have a single-page app (SPA) with no corresponding backend server, your SPA should request new tokens on login and store them in memory without any persistence. To make API calls, your SPA would then use the in-memory copy of the token.

No persistence means no localStorage, but we can use netlifyIdentity from the Netlify Identity Widget to hydrate the user state instead of using localStorage. In doing this, we are sort of using Netlify as the backend since they are handling authentication and the workflows involved, so it only makes sense to have the service hydrate the state with user authentication information whenever needed.

The function that gets user information is netlifyIdentity.currentUser();. See below for a basic example on its use to hydrate the user state:

import netlifyIdentity from "netlify-identity-widget";

netlifyIdentity.init();
const currentUser = netlifyIdentity.currentUser();

export const state = () => ({
  currentUser: currentUser
});

export const mutations = {
  SET_USER(state, currentUser) {
    state.currentUser = currentUser;
  }
};

export const actions = {
  setUser: ({ commit }, payload) => {
    commit('SET_USER', payload)
  }
};

When a user is not logged in, then netlifyIdentity.currentUser(); will return null. The SET_USER mutation will set the state to the currentUser returned by the login action of Netlify Identity, or it can set it to null after the logout action. If the page is refreshed or closed and revisited, then netlifyIdentity.currentUser(); will rehydrate the user state according what it sees fit to the SPA.

See the updated example of an example Nuxt.js SPA with this implemented (source).