Professional Documents
Culture Documents
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.
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 🙂
/
Compare your security to the industry best practices! Take the test
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:
1 <template>
2 <div>
3 <form class="login" @submit.prevent="login">
4 <h1>Sign in</h1>
5 <label>User name</label>
When the user has filled the inputs and clicked Login, we execute the login method.
1 ...
2 methods: {
3 login: function () {
4 const { username, password } = this
5 this.$store.dispatch(AUTH_REQUEST, { username, password }).then(() => {
6 this.$router.push('/')
7 })
8 }
9 }
10 ...
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.
15 // in your vue component
16 import {myLoginRoutine} from 'the-created-shared-file-containing-auth-api-logic'
17 ...
18 methods: {
19 login: function () {
22 this.$router.push('/')
23 })
24 }
25 }
26 ...
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).
1 const state = {
2 token: localStorage.getItem('user-token') || ''
3 status: '',
4 }
Well they are actually both good solutions, with their own advantages and disadvantages. This
post and this page answer this question pretty well.
1 const getters = {
2 isAuthenticated: state => !!state.token,
4 }
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 🙂
1 const actions = {
2 [AUTH_REQUEST]: ({commit, dispatch}, user) => {
3 return new Promise((resolve, reject) => { // The Promise used for router redirect in login
4 commit(AUTH_REQUEST)
5 axios({url: 'auth', data: user, method: 'POST' })
6 .then(resp => {
7 const token = resp.data.token
8 localStorage setItem('user token' token) // store the token in localstorage /
8 localStorage.setItem( user-token , token) // store the token in localstorage
9 Compare your security to
commit(AUTH_SUCCESS, the industry best practices!
token) Take the test
10 // you have your token, now log in your user :)
11 dispatch(USER_REQUEST)
12 resolve(resp)
13 })
14 .catch(err => {
15 commit(AUTH_ERROR, err)
18 })
19 })
20 }
21 }
1 // basic mutations, showing loading, success, error to reflect the api call status and the token
2
3 const mutations = {
4 [AUTH_REQUEST]: (state) => {
5 state.status = 'loading'
6 },
7 [AUTH_SUCCESS]: (state, token) => {
8 state.status = 'success'
9 state.token = token
10 },
11 [AUTH_ERROR]: (state) => {
12 state.status = 'error'
13 },
14 }
A fairly simple API call from a module. The important bits are:
Logout
Since we are at it, let’s implement our logout logic in modules/auth.js:
1 const actions = {
2
3 ...
4
5 [AUTH_LOGOUT]: ({commit, dispatch}) => {
7 commit(AUTH_LOGOUT)
9 resolve()
10 })
11 }
12 }
/
Compare your security to the industry best practices! Take the test
When clicking on the logout button in one of your components responsible for logout:
1 methods: {
2 logout: function () {
3 this.$store.dispatch(AUTH_LOGOUT)
4 .then(() => {
5 this.$router.push('/login')
6 })
7 }
8 },
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.
If you start to create one login/logout action per authentication type that you have, you will
have a headache maintaining them.
In modules/auth.js
1 const actions = {
2 [AUTH_REQUEST]: ({commit, dispatch}, user) => {
3 return new Promise((resolve, reject) => {
4 commit(AUTH_REQUEST)
5 axios({url: 'auth', data: user, method: 'POST' })
6 .then(resp => {
7 const token = resp.data.token
8 localStorage.setItem('user-token', token)
9 // Add the following line:
10 axios.defaults.headers.common['Authorization'] = token
11 commit(AUTH_SUCCESS, resp)
12 dispatch(USER_REQUEST)
13 resolve(resp)
14 })
15 .catch(err => {
16 commit(AUTH_ERROR, err)
17 localStorage.removeItem('user-token')
18 reject(err)
19 })
20 })
21 },
24 commit(AUTH_LOGOUT)
25 localStorage.removeItem('user-token')
27 delete axios.defaults.headers.common['Authorization']
28 resolve()
29 }) /
29 })
30 Compare
} your security to the industry best practices! Take the test
31 }
auth.module.js hosted with ❤ by GitHub
view raw
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.
25 component: Home,
26 },
27 {
28 path: '/account',
/
29 name: 'Account',
Compare your security to the industry best practices! Take the test
30 component: Account,
31 beforeEnter: ifAuthenticated,
32 },
33 {
34 path: '/login',
35 name: 'Login',
36 component: Login,
37 beforeEnter: ifNotAuthenticated,
38 },
39 ],
40 })
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 🙂
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
1 created: function () {
2 axios.interceptors.response.use(undefined, function (err) {
3 return new Promise(function (resolve, reject) {
4 if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
5 // if you ever get an unauthorized, logout the user
6 this.$store.dispatch(AUTH_LOGOUT)
7 // you can also redirect to /login if needed !
8 }
9 throw err;
10 });
11 });
12 }
Conclusion
What have we achieved here?
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.
Thibaud
R E L AT E D TA G S
Vue.js
65
Leave a Reply
34 31 3
41
Gavin Henry
/
Thibaud
Compare your security to the industry best practices! Take the test
Guest 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
N0
Thibaud
Thanks for taking time to write your comment regarding this matter !
Author
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.
jpic
Thibaud
Great question, i have added a note in the article regarding this question.
Author
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 🙂
/
Compare your security
2 to the industry best practices!
Reply Take the test
2 years ago
jpic
Younes
i think best way to do it, each time you use a specific function or call specific page you
Guest
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
Hi! great post. You have summarized perfectly what I’ve been learning for the last 4 months
Guest 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
Hi 🙂
Guest
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.
Cheers !
Sebastián Poliak
How about to polling in the frontend each 15 minutes and send a request to
Guest
for example /refresh and refresh the token?
Thibaud
Looks fine to me !
Guest
Raphael Castro
Thibaud
/
Compare your You are rightto
security ! Ithe
did forgot to addbest
industry the necessary import line,
practices! Takeit’s the
fixed test
🙂
Author
0 Reply 1 year ago
jing
thank you
Guest
David
just a note that, technically, log out should actually invalidate the token server side, not just
Guest delete it client side.
Thibaud
Paulo Imon
I tested the demo at this link: https://vue-auth-example.netlify.com and added a token with
Guest 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!
Thibaud
Since the provided repo example is without any backend, it mocks all api call to fake
Author
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 !
Paulo Imon
All right, Thibaud! I understand it now! Thank you for your reply!
Guest
Eric Giesberg
Great Tutorial!
Guest 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.
Thibaud
A possible solution is to look for the value of isAuthenticated when mounting the app:
Author
https://github.com/sqreen/vue-authentication-
example/blob/4c22da79f614968770a5610c55bd252deed0fb56/src/App.vue#L24
/
Compare your security to the industry best practices! Take the test
In this case its a vuex getter, but you can also check directly your cookie/localStorage
Mirco Attocchi
Bodahn
And what about SSR? Both cookies and localStorage are not acceptable solutions.
Guest
0 Reply 1 year ago
Thibaud
Sadly i have no experience in SSR :(. I’d be glad to hear about it in the comments !
Author
iCloud Support
Luis
Good work.
Guest
Many thanks from Spain
Stefan Nieke
Why is “isAuthenticated: state => !!state.token” future proof? If I write “false” in the
Guest localStorage your function returns true.
Thibaud
You are correct ! In this case if you need to handle a false you need to adapt your
Author
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 ?
Stefan Nieke
Why not adding a new state “state.isAuthenticated” that you set to true when
Guest
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?
/
Compare your security to the industry best practices! Take the test
Aaron
Tamrat Assefa
Great read. But I have one question. Isn’t the localStorage user-token value accessible by
Guest 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?
Thibaud
With a custom token, your api calls should fails since your backend should fail auth.
Author
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 !
@KayodeRock
The one issue I am having with this is if you log out and click on the back browser back
Guest button, the beforeEnter hook, does not seems to fire up, therefore the page is visible even
though I just logged out.
Thibaud
You are correct indeed. If this page has user related page (which i guess has since it
Author
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 ?
@KayodeRock
Fine, what I did was to use the beforeEnter hook on the router object. This fires
Guest
up every single time any route is accessed. Then I verify the user for that route,
I used the meta key on each route to specify who can access the route.
such as https://router.vuejs.org/guide/advanced/meta.html
Thibaud
ah nice indeed 🙂
Author
@KayodeRock
Zulko
Thank you for taking the time to write this, very informative and useful.
/
Guest Compare your security to the industry best practices! Take the test
0 Reply 1 year ago
Shelby
Thibaud
It’s up to you, it’s just a var pointing to a string that will be your action type. In this
Author
case its here: https://github.com/sqreen/vue-authentication-
example/blob/master/src/store/actions/auth.js . Hope it helps 🙂
Raj
Just wondering, why is it that you chose to store the strings in a file?
Guest
Additionally, is it possible to pass .dispatch the action as a function instead
of a string?
massi
Hello @Thibaud ! First of all thank you for this blog post, it is quite of interesting. I do have
Guest 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
Thank you for this great article, the article itself and the comments below helped me a lot.
Guest
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?
Thibaud
Indeed this is useless here. it’s most-likely an habit i have from other projects.
Author
This attribute is used to show loading on the initial request but not on the following
request !
Hopefully it helps 🙂
Luthfi
Nice. But how can I retrieve tokens after logging in from the server. SSO is like a social
Guest media login. vue is only a login client and the server is laravel. sorry my english is so bad. lol
Thibaud
Sadly i have no experience with SSO and Laravel hopefully someone reading this /
Sadly i have no experience with SSO and Laravel, hopefully someone reading this
Author
Compare your comment
securitymay
to help
the you out!
industry best practices! Take the test
JWT
Rob Brain
You haven’t factored in saving the state of a component when a token expires. What if
Guest 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
not that good with es6 yet. why did you write [AUTH_LOGOUT] instead of AUTH_LOGOUT in
Guest the store declarations?
Josué Artaud
That’s ES2015 computed property name feature. It allows you to use a constant as a
Guest
method name.
TOTO
Hello,
Guest
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
ew
VUEX state arent persist, except using vuex persist, whenever u go away from the
Guest
webApps the state is destroyed.
Shrad
Firstly, I’d thank you since the tutorial and all the comments helped me alot though I’m
Guest 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.
Steve
Bit late to the party but you pass the whole response object not just the token value here:
/
Guest
Compare your security to the industry best practices! Take the test
// line 11 on actions
commit(AUTH_SUCCESS, resp)
[AUTH_SUCCESS]: (state, token) => {
state.status = 'success'
state.token = token
}
is this intended ?
Hi Thibaut,
Guest
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
makri
Joseph
Nice article. I like how the Vuex actions are using dynamically interpolated constants instead
Guest of hard-coded keys/strings.
Ryan
Great article how would you handle refresh tokens though? Pretty well covered everything
Guest else. Great job. Also vue router can group all of your auth routes keeping your code DRY.
quan
JP
First of all, fantastic article and associated GitHub repo, thank you so much for taking the
Guest time to do this.. quick question though,
here, in main.js:
take care of it? As in, every time you actually need that in the header, it will be added by
your previous code?
Regards.
Y O U M AY A L S O L I K E
View Post
Serverless JWT
Dev
authentication with
Netlify and Zeit
Thibaud January 28, 2020
View Post
View Post
SEARCH
/
RECENT POSTS
Compare your security to the industry best practices! Take the test
C AT E G O R I E S
Dev (51)
DevOps (13)
Java (3)
JavaScript (7)
Node.js (11)
Python (18)
Ruby (13)
Security (90)