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 issue 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 projects have different authenticated behavior. Projects can be just a loading screen until you are logged in (Gmail), or a view of the application without 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. If 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 cover 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 future proof. By having this getter, you separate data from app logic making it future 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 redirecting them. 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 uses 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 navigation 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 of 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. And if you’re interested in protecting your apps at runtime, try Sqreen for free.

86 comments
    1. 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 🙂

      1. 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 JavaScript running on your site will have access to web storage, and because of this can be vulnerable to cross-site scripting (XSS) attacks.

        Cookie Disadvantages

        Cookies can be vulnerable cross-site request forgery (CSRF or XSRF) attacks. This type of attack occurs when a malicious web site causes a user’s web browser to perform an unwanted action on a trusted site where the user is currently authenticated. This is an exploit of how the browser handles cookies. Using a web app framework’s CSRF protection makes cookies a secure optionfor storing a JWT. CSRF can also be partially prevented by checking the HTTP Referer and Origin header.

        So if you don’t understand much you might think yea I can choose what ever. Or maybe I should stay away from cookies cause this whole paragraph about CSRF seems way too long and scary so better use localstorage.

        Unfortunately this will be the wrong thing to do.
        While CSRF is a serious issue it is relatively easy to defend against (by adding and checking for a CSRF token). on 2013 CSRF was ranked 8th place on OWASP top 10 vulnerability but at 2017 it was removed from the list since it seem like the awareness for it helped mitigate it a lot.

        XSS on the other hand is really hard to protect against. the more complex and larger your UI is the more chances you have for having XSS on your site. Deciding how and if to sanitize a certain string is diffrent for each context and a lot of time the developer has to decide and implement them for the specific use case. While on 2013 XSS was ranked 3rd place on OWASP top 10 at 2017 it was still ranked 7th place so it is still an issue many sites are struggling with

        It’s seem a bit weird to explain the difference in severity of XSS and CSRF here since one of the main selling point of sqreen is XSS protection. So I’m sure you are aware of all this issues.

        If we ignore the security issues in most use cases using Cookies is a lot simpler than using localstorage since the browser will send them in automatically. So without changing any of the UI code you could add in Cookies for auth. Actually you could keep this tutorial as is and change the meaning of token to be a CSRF token and not the actual session token and have almost exactly the same article explaining how to get a secure auth (You probably can remove all the localstorage stuff since there no need to keep the token between sessions).

        I hate to be a cynic but the only way I see using localsorage for storing session key being considered as best practice over cookies is if I’m in the business of offering XSS protection

        1. Thanks for taking time to write your comment regarding this matter !

          It really wasn’t the goal of this article to treat localStorage vs cookie since it’s very debatable subject and i would rather leave it to other articles treating only this matter!

          As you said, you can also switch the code here from localStorage and Cookies fairly easily, well it was meant to because i try to not instigate any strong opinions on this exact matter.

          I will still add a note in the article !
          Thanks for your time 🙂

          1. Actually you should store auth tokens in httpOnly cookies, that means that it should not be visible to JS at all.
            LocalStorage is vulnerable to XSS, unlike httpOnly cookies (which is what explains the link kindly shared by Thibaud). Also your cookie should be set to secure to prevent it from leaking on the network through clear http transactions.
            To make an auth cookie non visible to JS and as such protected from XSS as recommended by OWASP: the above tutorial won’t work, because JS won’t ever see that cookie.
            Pretty surprising from a security blog to propagate such misconceptions …

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

      They both have advantages and disadvantages, there is no clear winner. It’s up to the dev to choose the best solution according to it’s preferences and product.

      i advise you to read this post if you want to learn more about it 🙂

        1. @jpic One definition of insanity is repeating yourself and thinking that you will get a different result…

    3. 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 get the admin privileges by limiting that on the backend logic), and each time when something happened to the backend get the role from that id and do the operation or rejet base on the role setup, i think what all you need to do, is Navigation Guards and make your backend(pref API) recheck the user role in each action.

  1. 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 that?

    1. 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 !

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

  2. 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?

  3. 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!

    1. Since the provided repo example is without any backend, it mocks all api call to fake answers. The Authentication is made by presence or not of the token for simplicity sake.

      Token validation should be done by making an API call to your backend with the token and check its answer. Usually if you have a 401 response you know the token isn’t valid. Hope it helped !

  4. 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.

    1. A possible solution is to look for the value of isAuthenticated when mounting the app:

      https://github.com/sqreen/vue-authentication-example/blob/4c22da79f614968770a5610c55bd252deed0fb56/src/App.vue#L24

      In this case its a vuex getter, but you can also check directly your cookie/localStorage ( depending on your token storage strategy ).

      This line says: When the app is mounted, fire a user fetch action, the vuex module responsible for this action will then handle it

      Does it help you ?

  5. 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.

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

    1. You are correct ! In this case if you need to handle a false you need to adapt your code !
      My opinion on this matter is that if you send false as a token, you should logout the user and hence empty the token localstorage too.
      What do you think ?

      1. Why not adding a new state “state.isAuthenticated” that you set to true when the AUTH_REQUEST was a success. Otherwise “state.isAuthenticated” is always false. The getter than just returns the state. “isAuthenticated: state => !!state.token” checks if there is some string but it isn’t a real check if the is user is authenticated. What do you think?

        1. Totally possible :).
          isAuthenticated strategies really depends on your use case and what you feel comfortable with.

  7. 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?

    1. With a custom token, your api calls should fails since your backend should fail auth. And when you have failed auth you have your failing auth routine that execute and empty the localstorage and redirect you to login for example !

  8. 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.

    1. You are correct indeed. If this page has user related page (which i guess has since it has beforeEnter hook), you should get an unauthorized api call. This would result as a redirect to login.
      I agree It’s not perfect, do you have any idea to handle it better maybe ?

  9. I get an error saying AUTH_REQUEST is not defined.

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

    Where is AUTH_REQUEST defined?

      1. Just wondering, why is it that you chose to store the strings in a file? Additionally, is it possible to pass .dispatch the action as a function instead of a string?

  10. 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 on the interceptors (It’s strongly recommended to use 3 different interceptors (one for the auth request, one specifically for the the refresh, and the last one for the not auth request). This is required in order to avoid a circular dependence issue raising when using only one interceptor.

    Best and thanks again

  11. 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?

    1. Indeed this is useless here. it’s most-likely an habit i have from other projects.

      This attribute is used to show loading on the initial request but not on the following request !

      – first auth request, show loading
      – another auth request to refresh the auth state, no need to show a loading.

      Hopefully it helps 🙂

  12. 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

  13. 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.

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

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

  15. 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

  16. 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.

  17. Bit late to the party but you pass the whole response object not just the token value here:

    // line 11 on actions
    commit(AUTH_SUCCESS, resp)

    [AUTH_SUCCESS]: (state, token) => {
    state.status = 'success'
    state.token = token
    }

    is this intended ?

  18. Great post Thibaud,

    I am having a little issue. After I dispatch a successful authentication, I perform a router.push(“/home”), there I get Uncaught (in promise) undefined, after the “ifAuthenticated” call. Do you have an idea? Thanks

  19. Hi Thibaut,

    Wouldn’t just setting a user-token key in the localstorage give you access to a protected route without login??I assume there still needs to be a server side check to make sure the client side data isnt compromised??

    Thx in regards
    Kurt

  20. Nice article. I like how the Vuex actions are using dynamically interpolated constants instead of hard-coded keys/strings.

  21. Great article how would you handle refresh tokens though? Pretty well covered everything else. Great job. Also vue router can group all of your auth routes keeping your code DRY.

  22. First of all, fantastic article and associated GitHub repo, thank you so much for taking the time to do this.. quick question though,

    here, in main.js:

    const token = localStorage.getItem(‘user-token’)
    if (token) {
    axios.defaults.headers.common[‘Authorization’] = token
    }

    why is this necessary if you add the header on every request and remove it on logout in Vuex anyways? I understand that when you refresh it’s gone, but doesn’t your previous Vuex code here:

    // Add the following line:
    axios.defaults.headers.common[‘Authorization’] = token

    take care of it? As in, every time you actually need that in the header, it will be added by your previous code?

    Regards.

  23. In the git repo, you replaced the axios calls with utils/api.js, which I like but I think the signature doesn’t match the call. See e.g. auth.js module, you do apiCall({ url: “auth”, data: user, method: “POST” }) whereas the signature is ({ url, method }). Just a small details.

  24. In the login.vue you have import { AUTH_REQUEST } from “actions/auth”; but how is it able to find it? shouldn’t it be from “../../store/actions/auth”?

  25. I am new to SPA’s but this doesn’t make sense to me. How can you possibly know if the token is legit without checking with the server before allowing access to your protected routes?

    I realize that you send it in the header with subsequent requests after logging in which is great for the server side but doesn’t help with secure routes on the client side. With this example it would be easy to spoof the token in the local storage and gain access to the protected routes since you are not checking the validity of the token.

    It seems to me that routing is best left to the server unless the back-end is truly a rest API that is stateless. It seems silly to me to have to send the token to the server to check its validity before allowing access the protected routes on the client side.

    I would love to know if it is possible to use single file components with a classic client/server model where the server handles routing.

  26. Pingback: VueJS | Pearltrees
  27. Salut Thibaud, Can you help me in this please ? Merci
    I’m trying to store Token via axios. It gives me an error TypeError: _Token__WEBPACK_IMPORTED_MODULE_0__.default.isValid is not a function. My Login.vue is methods:{
    login(){
    axios.post(‘/api/login’,this.form)
    .then(res => User.responseAfterLogin(res))
    .catch(err => {
    console.log(err);
    });
    User.js
    import Token from ‘./Token’
    import AppStorage from ‘./AppStorage’

    class User{

    responseAfterLogin(res){
    const access_token = res.data.access_token
    const username = res.data.name
    if(Token.isValid(access_token)) {
    AppStorage.store(access_token,username)
    }
    }
    hasToken(){
    const storeToken = localStorage.getItem(‘token’);
    if(storeToken){
    return Token.isvalid(storeToken)? true : false
    }
    false
    }
    loggedIn(){
    return this.hasToken()
    }
    name(){
    if (this.loggedIn()){
    return localStorage.getItem(‘user’);
    }
    }

    id(){
    if (this.loggedIn()){
    const payload = Token.payload(localStorage.getItem(‘token’));
    return payload.sub
    }
    return false
    }
    }

  28. Thanks for great tutorial, I still learning vuejs. But I have problem like this, open Inspect Element -> Application -> local Storage, Double click add user-token key with value anything, now I can access protected page without login. How to prevent this problem?

  29. Pingback: vue js jwt
  30. Pingback: vue login - LoginCast.Com
Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like