Authentication Best Practices for Vue

Authentication in Vue.js

Introduction

Whenever you start to get serious with a project, you will most likely face the issues of how to handle client-side token-based authentication.

You will have to answer these questions:

  • How do I store my user’s token?
  • How do I redirect the user after authentication actions ( Login/ Logout )?
  • How do I prevent route access to authenticated and unauthenticated users?

This article will walk you through these questions and try to illustrate with clear code and good practices in mind.

However, keep in mind that all project have different authenticated behavior. Projects can be just a loading screen until you are logged in (Gmail) or still being able to see the application but don’t have access to every feature (Amazon), so you will probably have to adjust what I will describe here.

Before we start

I have made this repo if you want to run some code.

We use Vuex as the global state library. Vuex is especially suited for auth management since it’s application scoped. You don’t want to use Vuex, no worries we also give some code example without Vuex 🙂

We also use the axios library for the ajax calls.

Finally, this article will not show how to implement the backend side of the authentication process. We will only focus on the client side.

Login

Let’s start with a simple login form:

When the user has filled the inputs and clicked Login, we execute the login method.

Here is the first important bit of this code snippet.

  • Vuex actions returning promises.

this.$store.dispatch(AUTH_REQUEST, { username, password }).then(...)

Did you know? You can also treat action dispatch as promises. Now we can react to successful logins from the component. Allowing us to redirect accordingly.

The same code without Vuex:

Vuex Auth Module

Let’s now look at this auth module:

First, let’s initialize the state.

We will have the token field (using local storage stored token if present) and a status field, representing the status of the API call (loading, success or error).

What about using localStorage and not cookies ?

Well they are actually both good solutions, with their own advantages and disadvantages. This post  and this page answer this question pretty well.

Some useful getters:

The ‘isAuthenticated’ getter can seem overkill, however it’s a great way to keep your authentication futur proof. By having this getter, you separate data from app logic making it futur proof 🙂

Now the action:

And the mutation:

A fairly simple API call from a module. The important bits are:

  • Token state being initialized by its local storage value, if possible.
  • The Authentication request action returns a Promise, useful for redirect when a successful login happens.
  • Good practice: pass the login credentials in the request body, not in the URL. The reason behind it is that servers might log URLs, so you don’t have to worry about credential leaks through logs.

Logout

Since we are at it, let’s implement our logout logic in modules/auth.js:

When clicking on the logout button in one of your components responsible for logout:

In this case, logging out for us means clearing out the user’s token and redirect him. If you need to perform other Vuex state changes, listen to this AUTH_LOGOUT action and commit.

You can also add a token DELETE request  in your action to delete your user token session when logging out.

Try to keep it simple, as few actions as possible to Logout/Login.

If you start to create one login/logout action per authentication type that you have, you will have a headache maintaining them.

Use the token

Now that we have managed to retrieve the token and store it, let’s use it!

The following example is using Axios and its default headers.

in modules/auth.js

Now after login, all the Axios calls have the authorization header set to your token. All your API calls are authenticated! And when logging out, we delete the authorization header.

Auto authentication

Right now if we refresh the app, we do have the state correctly set to the previous token. However, the authorization Axios header isn’t set. Let’s fix it!

In your main.js:

Now your API calls are authenticated when refreshing your app after login!

Authenticated routes

Now you probably want to restrict access to your routes depending on whether they are authenticated or not.

In this case, we want only authenticated users to reach /account.

And unauthenticated users should only be able to reach /login and /.

Here we use naviguation guards, they allow us to put conditions on routes access that we use to our advantage in conjunction with the Vuex store.

Note 1: If you do not wish to use Vuex, you can still check for token presence in the local storage rather than looking at the store getters 🙂

Note 2: Ed @posva, maintainer of vue-router also advises the usage of meta attributes, check it out 🙂

Handling the unauthorized case scenario

…but wait, what if the token is expired, what if the user is unauthorized?

No worries here.

Using Axios, you can intercept all responses, and especially the error response. Just check for all unauthorized responses (HTTP 401) and if so, dispatch a logout action.

In your App.vue

And we are good to go!

Conclusion

What have we achieved here?

  • Isolated authentication logic from the app and other libs.
  • We won’t need to explicitly pass tokens to every API call.
  • Handle all unauthenticated API calls
  • We have auto authentication
  • We have restricted routes access

What have we learned?

  • Separation of concerns
  • Avoid side effects
  • Action dispatch can return promises

This should give you a pretty solid starting point for handling all your API calls in your app.

Hopefully, this will be helpful for your future projects! If you’re interested in learning how to avoid cross-site scripting (XSS) in your Vue.js app check out my previous post.

54
Leave a Reply

avatar
26 Comment threads
28 Thread replies
3 Followers
 
Most reacted comment
Hottest comment thread
31 Comment authors
ShradTOTOJosué ArtaudfogxYounes Recent comment authors
  Subscribe  
newest oldest most voted
Notify of
Gavin Henry
Guest
Gavin Henry

Should you be storing tokens in localStorage?

Thibaud
Guest
Thibaud

Great question, i have added a note regarding this question.
You have 2 possibilities:
– localStorage ( or sessionStorage )
– cookies

They both have advantages and disadvantages, there is no clear winners. It’s up to the dev to choose the best solution according to its preferences and product.
I advise you to read this post: https://auth0.com/docs/security/store-tokens if you want to learn more about it 🙂

Gavin Henry
Guest
Gavin Henry

Thanks. Will do.

N0
Guest

I don’t think there is no clear winner. OWASP guidelines specifically advice against keeping credentials in localstorage – https://www.owasp.org/index.php/HTML5_Security_Cheat_Sheet ” Do not store session identifiers in local storage as the data is always accesible by JavaScript. Cookies can mitigate this risk using the httpOnly flag.” The Auth0 article you mentioned make it seem like using localstorage and coockies both have similar length of disadvantage and from security perspective it looks like there is only one security issue in each you should care about – Web Storage Disadvantages … Web Storage is accessible through JavaScript on the same domain so any… Read more »

Younes
Guest
Younes

i think best way to do it, each time you use a specific function or call specific page you have to get the current user role and base on that get specific page via router guard and again limit database operation for roles as well, because if its in localStorage then the front it can be easily modified and then he can get the admin part ,so the best way you store the session id in localStorage or even y can store role but can be modified(even if he changed it as admin and got admin interface he ll not… Read more »

diferno
Guest
diferno

Hi! great post. You have summarized perfectly what I’ve been learning for the last 4 months surfing the web 🙂 The only points that I’m missing is the functionality in interceptors for the case that the user tries to access a page without logging in, loggs in and the interceptors should redirect to the previous path. This is how I do it on my projects: if (to.query.redirect && store.getters.isLoggedIn) { return next(to.query.redirect) } Another part that I’m still struggling with, is the refresh tokens when the jwt expires. I don’t want the user to manually login again. Any tip on… Read more »

Thibaud
Guest
Thibaud

Hi 🙂
In my opinion the best practice here is the backend handling the token refresh (refresh after each user auth). However, i have seen people handle this front end side indeed.

What i have seen is the backend sending a specific http code (can be anything else) response to a login request ( when the user’s token is about to be outdated ). An interceptor would catch this specific code and fire a token refresh.

What do you think about those two solutions ?

Cheers !

Sebastián Poliak
Guest
Sebastián Poliak

How about to polling in the frontend each 15 minutes and send a request to for example /refresh and refresh the token?

Thibaud
Guest
Thibaud

Looks fine to me !

Raphael Castro
Guest
Raphael Castro

Hi, great post! Thanks a lot for this content.

I have one question: When you use the store inside the navigation guards, “store” variable will not be undefined?

jing
Guest

thank you

David
Guest

just a note that, technically, log out should actually invalidate the token server side, not just delete it client side.

Paulo Imon
Guest
Paulo Imon

I tested the demo at this link: https://vue-auth-example.netlify.com and added a token with this script in the console: `localStorage.setItem(‘user-token’, ‘some-value-here’)`.
Then after the page has been reloaded, I was logged in.
I’m asking, because all scripts are on the front-end and I can see it via console. Thus, how can I validate the token stored in `localStorage`?
Thank you for now!

Eric Giesberg
Guest
Eric Giesberg

Great Tutorial!
Having some problems that I can’t seem to repeat (reliably) with the login process:
Login in on one Tab
Refresh first tab, everything seems to be working
Open up second tab, the token is pulled successfully but the axios call to load my user profile doesn’t seem to run. Though sometimes either opening developer mode in Chrome or closing the npm instance seems to kickstart it into completing the query.

Maybe its a misunderstanding of vuex on my part.

Mirco Attocchi
Guest
Mirco Attocchi

Thank you! Good tutorial and track to follow!

Bodahn
Guest

And what about SSR? Both cookies and localStorage are not acceptable solutions.

iCloud Support
Guest

Authentication is very much important because it describes the authenticity of a person or website. You must use authentic website and authentic products. For that, you must look at the copyright things and go forward with it. The article has helped me a lot.

Luis
Guest
Luis

Good work.

Many thanks from Spain

Stefan Nieke
Guest
Stefan Nieke

Why is “isAuthenticated: state => !!state.token” future proof? If I write “false” in the localStorage your function returns true.

Aaron
Guest
Aaron

This helped me a LOT. Thanks for the post!

Tamrat Assefa
Guest
Tamrat Assefa

Great read. But I have one question. Isn’t the localStorage user-token value accessible by the user? Which means the user can create a random user-token and the system treat the user as logged in, right? Is this the intended behavior? What am I missing here?

@KayodeRock
Guest
@KayodeRock

The one issue I am having with this is if you log out and click on the back browser back button, the beforeEnter hook, does not seems to fire up, therefore the page is visible even though I just logged out.

Zulko
Guest
Zulko

Thank you for taking the time to write this, very informative and useful.

Shelby
Guest
Shelby

I get an error saying AUTH_REQUEST is not defined.

In this syntax,
[AUTH_RESUEST]: ({commit, dispatch}, user) => {…}

Where is AUTH_REQUEST defined?

massi
Guest
massi

Hello @Thibaud ! First of all thank you for this blog post, it is quite of interesting. I do have a similar approach, but I do also add a token refresh in the request(interceptor). I use a time limit on the token expiration to trigger a refresh token, a logout or nothing and just set the access token in the header. I find this approach much more clean than the widely adopted on the internet (making the refresh in the response). I also don’t have the need to deal with concurrent request, or previous request. The technique requires particular attention… Read more »

Ahmet EGESEL
Guest
Ahmet EGESEL

Thank you for this great article, the article itself and the comments below helped me a lot.

One thing I’m curious about is a state variable, which you didn’t mention in this article, in the auth.js store module: hasLoadedOnce. What is its purpose?

Luthfi
Guest
Luthfi

Nice. But how can I retrieve tokens after logging in from the server. SSO is like a social media login. vue is only a login client and the server is laravel. sorry my english is so bad. lol

JWT
Guest
JWT

Great article, thx Thibaud! I am learning Vuex with your post

Rob Brain
Guest
Rob Brain

You haven’t factored in saving the state of a component when a token expires. What if they’re doing loads of work and then hit “submit” or “save” or whatever but it fails because the token expires, so you redirect to login and the state of that component has gone when they return to that component after logging in.

fogx
Guest
fogx

not that good with es6 yet. why did you write [AUTH_LOGOUT] instead of AUTH_LOGOUT in the store declarations?

Josué Artaud
Guest
Josué Artaud

That’s ES2015 computed property name feature. It allows you to use a constant as a method name.

trackback

[…] I started my Vue.js application with the amazing Vue CLI and played around with the Google API to get some data. No backend needed […]

TOTO
Guest
TOTO

Hello,

I am wondering one thing, since VueX (on the same princip like Redux) is a state manager, accessible by all the components of your WebApp, why on earth would you use localStorage ? You can access your token in the VUEX state everywhere, no need to store such important information in localStorage

Shrad
Guest
Shrad

Firstly, I’d thank you since the tutorial and all the comments helped me alot though I’m pretty new to Vue, been receiving unknown action type: AUTH_REQUEST error and this halts the dispatching of event. I guess it’s an issue with namespaces? Did not find that in your code hosted on github.

You May Also Like