JWT Authentication in Vue/Nuxt — The Right Way
Authentication in SPAs is often a hot topic, and even more-so for those who aren’t sure of the best method for implementing an authentication system with all the necessary features and one that can handle the most common edge cases.
Although we won’t cover writing the backend JWT or authentication implementation, there are various articles that describe the best methods for various languages and frameworks, depending on your needs. Regardless of your choice, you’ll want to make sure to implement the three core features necessary for the frontend — registration, login and access token refreshing via refresh tokens. We’ll discuss a basic implementation of some of these endpoints, but they are still completely flexible. It’s also assumed that you have already setup a basic Nuxt application in universal rendering mode.
Getting Started
For the frontend, we’ll be making use of three essential packages — vuex-persistedstate, js-cookie and @nuxtjs/axios. The first allows us to persist Nuxt module state into a store of our choosing (cookies!), which will allow us to store tokens and user data accessible to both the Nuxt server, as well as the client, which in turn allows for authenticated calls from both ends. The second will make parsing cookies easier, while the last is a common Nuxt package, providing an all-inclusive package for HTTP calls.
Let’s start by installing those.
npm install --save vuex-persistedstate js-cookie @nuxtjs/axios
VueX State Persistence
To make authenticated API calls from both the server and browser (client), we need to ensure that the tokens are accessible from both ends. vuex-persistedstate simplifies this, and with the help of js-cookie will persist the tokens to a cookie.
After installing the packages, we’ll need to configure the vuex-persistedstate with a plugin.
Don’t forget to add this plugin to your nuxt.config.js!
plugins: [
'~/plugins/local-storage',
],
VueX Store
We’ll also need to setup the VueX store, which will be where we store data about the user, the access token and refresh token. We’ll also need to include actions for making API calls to register, login and refresh a user, as well as mutations to commit the returned data to the state.
Although the structure is easily modifiable, you should end up with something like this…
Now that we have a state in place, you’ll need to create form components for the login and registration pages, which also won’t be covered here. Essentially, your forms should call the authentication module actions to login or register the user.
With the state and forms in place, and the ability to authenticate users, we can implement authenticated API requests!
Authenticated API Requests
For this portion, we’ll make use of Axios’ built-in interceptors feature, which allows us to modify requests and responses, as well as capture all errors. Thankfully, @nuxtjs/axios provides first-class support for this feature.
We’ll use a request interceptor to attach the access token to every API request.
This plugin is fairly straightforward — capturing every request and if the user is authenticated, adding a new Authorization header to the request.
Don’t forget to add this plugin to your nuxt.config.js, just as before.
plugins: [
'~/plugins/local-storage',
'~/plugins/axios',
],
Refresh Tokens
However, what happens when the access token expires? For security reasons, access tokens shouldn’t be long-lived and should be easily revocable if necessary.
When access tokens expire or become invalid but the application still needs to access a protected resource, the application faces the problem of getting a new access token without forcing the user to once again grant permission. To solve this problem, OAuth 2.0 introduced an artifact called a refresh token. A refresh token allows an application to obtain a new access token without prompting the user.
— “Understanding Refresh Tokens” via auth0
To handle this, we can modify our existing interceptor plugin, adding an error handler to automatically attempt to refresh the access token in the event that it expires. Before we do so, let’s discuss how API should handle expired refresh tokens and the refresh API endpoint itself.
In the event of an expired access token, the API needs to inform the client that the token is no longer valid and needs to be refreshed. This is most often done by returning a 401 status code and some sort of descriptive payload.
The structure of the payload is completely flexible — but for simplicity’s sake, we’ll use this format for demonstration purposes.
Now that the client is aware of the expired token, it can move onto attempting to refresh the token, before eventually retrying the initial request.
As for the refresh endpoint — given a refresh_token
value via a POST request, the API should generate a new access token, and optionally a new refresh token that should be returned to the client. If the refresh token is expired, revoked or otherwise invalid, it can also return an error to let the client know that user cannot be re-authenticated and should be logged out.
With that in mind, let’s modify the existing interceptor plugin to add the error handler.
The plugin is documented for more specifics, but essentially the new interceptor checks if the error is related to an expired token, and then attempts to refresh the access token if present.
If the refresh succeeds, the Promise will return a copy of the original request, making the calling function completely unaware that the token was refreshed before receiving its expected response.
However, if the refresh fails for whatever reason (i.e. revoked, expired, or invalid refresh token), the interceptor will automatically logout the user and redirect them home.
With the final modification to the interceptor plugin in place, we can now make authenticated requests that will automatically refresh the access token if necessary!
If the access token is valid, we’ll get our expected response! If it’s expired or otherwise invalid, our plugin will attempt to refresh the token, and (assuming it’s successful) still return our expected response!
Before we wrap up, let’s make one last addition to our VueX store.
Nuxt provides the nuxtServerInit hook for the initial SSR request to the server. Using this, we can automatically refresh the access token when a logged-in user first connects to the server. Since we’re using SPA technology, the page is not often refreshed, so when it is we can assume that a short-lived access token is likely expired. This action needs to be added to your root store module.
Now, when the user navigates to your application via the URL bar or an external inbound link, we’ll automatically refresh their token if they were previously logged in.
Epilogue
Of course this tutorial doesn’t cover every edge case and isn’t 100% feature complete, but provides the essential implementation for universal client and server-side JWT authentication in a Nuxt application. Authentication is a critical part of every app, and should be thoroughly tested for security issues before releasing publicly.
Checkout all the code samples here or read more about refresh tokens here.