DEV Community

MrNaif2018
MrNaif2018

Posted on • Updated on

How to make nuxt auth working with JWT - a definitive guide

Introduction

Nuxt.js is a nice framework for creating both SSR and SPA apps easily in vue. It is easy to use, but sometimes there are some stuff which can block you for weeks.
This stuff for me was adding JWT authentification.

Backend situation

Let's assume the following situation:
We have a backend, serving a few endpoints:

  • /token - by sending json in form {"email":"example.com","password":"somepassword"}, if user exists and password is valid, it returns a pair of access token and refresh token
  • /refresh_token accepting json in form {"token":"refreshtoken"} returning new refresed access and refresh tokens
  • /users/me - returning current user information, could be anything based on your app. Any other endpoint is for authorized users only. Access token duration in my case was 15 minutes, and refresh token duration-7 days(basically the time I want user to be logged in without reentering credentials).

Frontend setup

Nuxt.js docs recommend using @nuxtjs/auth package.
It supports different auth schemes and stuff, but it doesn't support refresh token out of the box.
As we have quite a simple API, I picked up local auth scheme.

Login component

So, in login component, I have the following code:

What does this do? Well, as nuxt auth doesn't support refresh tokens saving with local scheme, to do this with minimal code changes, we do it manually.
We send request to /token endpoint, and if it succeeded, we save the token(in my case I disabled localStorage and left only cookies), save refresh token(local scheme doesn't support this,but the module itself does), and set authorization headers on axios instance(
this.$auth.ctx.app.$axios.setHeader('Authorization', 'Bearer ' + resp.data.access_token)
is redundant, but I just left it to make sure token is set :D)
Next, we fetch current user and manually save it in storage.
That is login scheme.

Nuxt config

We should do some configuration in nuxt.config.js:

We configure axios baseUrl to some default value, to avoid requests to the server itself and infinite loops(any value is fine, as it will get replaced by actual url in plugin).
Also we enable global loggedIn middleware.
Auth module has it's own auth middleware, but I'll return to that in a moment.
In auth module settings we disable localStorage(we want some security,right?), and set cookie expire time to 7 days(time when I want the user to get logged out).
Next, we configure our endpoints, it depends on how your backend works, in my case, I have /token in post method, no logout endpoint, and /users/me endpoint where data is in body(propertyName: false).

Next, we add two auth plugins(note, they're specified NOT in nuxt plugins, but in auth module plugins section.
~/plugins/axios.js configures axios baseUrl
and
~/plugins/auth.js makes refreshing work.
Note that we enable it client side only, as for some reasons it doesn't work server side(use ssr:false in older versions of nuxt).

Now, to the plugins!

~/plugins/axios.js:

It just configures baseUrl to don't type it everywhere (:
Note, store.state.env.URL is dynamically loaded env variable.
Should I write another post about building docker images once, and loading environment variables on server start?
Share your opinions in comments.

~/plugins/auth.js:

Ok, that's a big chunk of code!
Let's investigate what it does!
Strategy constant is local in our case, if you use a different name, change it.
FALLBACK_INTERVAL is used when no token is available(i.e. right after login), set it to your token expiry date in miliseconds(so it's 15 minutes or 900 seconds converted to milliseconds).
And multiply that by 0.75, because we want to refresh token a little bit before it's expiry time.

refreshTokenF is doing the refresh process.
It sends request to our refresh endpoint, if we have tokens provided.
Then it basically saves tokens to the storage, returning it's parsed expiry time.
If it failed, we log out(it means 7 days passed).

decodeToken function is parsing JWT token into it's data.

Now, to real plugin code:
First, we get $auth and $axios plugins from our app instance.
We try getting those tokens from our cookies(plugins are executed on page load), and fallback to our constant interval first.
If we have those tokens in our storage, we parse access token, and get it' s expiry time.
Also, we fetch the user, as when nuxt auth module fetches is, our baseUrl is not yet configured.
If expiry time is less then 0(token expired), we refresh that right away and update expiry time.
Finally, we use setInterval to refresh token at 75% of it's expiry time.

Middleware

And the final part, middleware.
Why do we need to reinvent the wheel? Because even if logged in, we'll get logged out in production, because server side you're not logged in, so the only difference between default auth middleware and ours is if (!process.client) check, as the middleware should be executed client side only:

Congratulations!

We did it!
As you can see, nuxt auth module is nice, but unfortunately requires some workarounds. I hope you found this article useful and won't spend weeks like I did trying to fix those strange bugs (:

I did those things while improving my opensource project: BitcartCC.
If you want to contribute to it or just see how I did it, check it out:

GitHub logo bitcartcc / bitcart-admin

BitcartCC Admin Panel

BitcartCC Admin Panel

CircleCI

This is the BitcartCC Admin Panel.

It is created to simplify usage of BitcartCC Merchants API, making adding or editing data easy, plus containing a checkout page which can be used by various integrations.

The admin panel always covers 100% of the Merchants API.

Live demo

Contributing

See CONTRIBUTING.md.




Oldest comments (61)

Collapse
 
innercitypressure profile image
InnerC#ity

Nice!

Collapse
 
mrnaif2018 profile image
MrNaif2018

Thank you! I usually don't write posts, but this is the thing you can't easily find on the internet (:

Collapse
 
marciks profile image
marciks

Awesome post!

Collapse
 
mrnaif2018 profile image
MrNaif2018

Thank you! Should I also write a post about dynamic environment variables loading in nuxt?

Collapse
 
kn8sk profile image
kn8sk

Yes please!

Thread Thread
Collapse
 
kn8sk profile image
kn8sk

Haven't really tried to implement this solution yet, but I wanted to thank you in advance! This is the exact problem I am currently facing with my nuxt/jwt project. My lack of experience in js / vue / nuxt made this impossible to solve on my own.

My ugly workaround was to set the jwt token to never expire on the backend, and hope the official support will come before I need to go to production. Relevant github issue, if you are interested - github.com/nuxt-community/auth-mod...

Collapse
 
mrnaif2018 profile image
MrNaif2018

Yeah! That's why I have written it. It is so hard to set up just this auth, I spend a loot of time doing it, and I came to this solution after lots of experiments. That's why I share it, I want to save people's time, and, possibly, point nuxt devs to such an inconvenience.
P.S. I am no js expert as well, it's just "science touch method" :D

Collapse
 
aisone profile image
Aaron Gong

Not only do you need to roll out your own refresh token mechanism. Handling 2FA authentication also needs some work...

Collapse
 
mrnaif2018 profile image
MrNaif2018

When I get to implementing 2FA I think I will make a new post showing how to avoid some difficulties (:

Collapse
 
jeppepepp profile image
JeppePepp

Hey!

Thanks for the great post. I cant get the $auth instance from the destruction of the app object in the auth.js plugin tho.

//auth.js
export default async function ({ app }) {
const { $axios, $auth } = app <------- $auth is not avaialbe i.e undefined

I dont really understand what I should do to get it avabile in the plugin.
Its availabe in the App it self.
Also $axios (and for med $vueitfy) is instantiated and avaialbe.

Thanks for any tips! :)

/J

Collapse
 
mrnaif2018 profile image
MrNaif2018

To get $auth instance on your app, your plugin should be specified as nuxt auth plugin, not normal nuxt plugin.
Recheck your nuxt.config.js file, I'll be glad to help!

Collapse
 
jeppepepp profile image
JeppePepp

Yes that was it! Thanks!

Collapse
 
adashi12 profile image
André Lehner

For anybody wondering where the " self. " comes from in the login component (and getting error messages about it not being defined), above the post request there should be:

const self = this

Basically it is used to access " this " inside the promise.
(not sure how clean this is, though)

Collapse
 
mrnaif2018 profile image
MrNaif2018

Yes, exactly. It was mentioned a line above in my full code, missed it (:
Anyone who visits the full repo can see the full code (:
Thank you for noticing this! I updated the gist and the post to use this instead, as(if I understand it correctly), when using arrow functions for .then callback this is not lost.

Collapse
 
sopon73293607 profile image
sopon

Great Post. One issue.

middleware: ["loggedIn"] in nuxt.config not working for me :(

if i set middleware: ["auth"] in nuxt.config that working.

i want to all site rrquired authenticaion without login + signup page

Collapse
 
mrnaif2018 profile image
MrNaif2018

Thank you! Strange that it didn't work as it is almost a full copy of auth middleware, but glad you got it resolved (:
In my setup there are login and signup pages, but it's up to you how to organize your site.

Collapse
 
sopon73293607 profile image
sopon

Thanks for quick reply :)

I don't understand why you use middleware: ["loggedIn"] :(
Can you please explain easyway. If you don't mind :)
Thanks

Thread Thread
 
mrnaif2018 profile image
MrNaif2018

It was because of rehydration errors, not matching content on server and client side, and by enabling this middleware client side only it solved the issues. if (!process.client) return; is the only change. Not DRY, I know (:
In near days I am going to hunt one more bug, will update the post after I fix that(axios dynamic base url not used, it requests local server and not the remote one).

Collapse
 
egorpavlikhin profile image
Egor Pavlikhin

Official guide on implementing JWT in Nuxt is here github.com/nuxt/docs/blob/master/e...

Collapse
 
mrnaif2018 profile image
MrNaif2018

Good guide,I saw that, but this article is not about implementing JWT which is indeed easy and supported, but for implementing JWT refresh functionality. Thank you for the useful link!

Collapse
 
egorpavlikhin profile image
Egor Pavlikhin

Yes, sorry, your article is great, but I found it looking for a simple JWT implementation guide in Nuxt, so someone might have come with the same query.

Collapse
 
mthnglac profile image
Metehan Gülaç

Greetings, it was a very useful post for me, first of all thank you :)

There is a problem for me. While browsing my portfolio website, it always puts me on the login page. I just want the authentication processes to work only when I enter the login page (for admin panel and post some staff. router path is /panel). how can i achieve this?

Collapse
 
mrnaif2018 profile image
MrNaif2018

With my setup, all pages are by default for authorized users only, with small exception. Use auth: false(disable at all) or auth: "guest"(viewable for only non-authorized users) on your pages which don't require auth(i.e. in export default block, like
export default {
auth: false,
....
}

Or, if all your pages by default don't require auth, you might want to change global auth middleware behaviour then by removing it at all from nuxt.config.js, and enabling only on individual pages, see:
auth.nuxtjs.org/guide/middleware.html

Collapse
 
mthnglac profile image
Metehan Gülaç

It has been a while since I read the documentation, I forgot this detail. Thank you very much.

I have observed something that I do not understand. $auth works very well while browsing the application and clicking on the router links. But when I try to enter a valid URL for $auth in the browser url part, the same page opens without authentication, the application does not recognize $auth at this stage.

For example: Authentication is requested when I click on the toolbar 'Panel Button' and '/login' page comes up. but if I write the same url (/panel) from the browser and enter it, it does not ask authentication, it enters '/panel' route directly.)

Thread Thread
 
mrnaif2018 profile image
MrNaif2018

Hmm, are you sure you have enabled auth middleware on that page?
Could you send a minimal snippet(repl.it, codesandbox, github/github gist or other sharing services) to reproduce that? I can look what is wrong.
Maybe try using built-in middleware and not the one from that post. In my situation it displays the page for a sec, and redirects to login page on client side. Probably custom middleware isn't needed anymore, I can't tell for sure as I am migrating to API tokens(randomly generated) with OAuth requests support and token revoking instead of JWT, it actually solved a loot of problems I had.

Thread Thread
 
mthnglac profile image
Metehan Gülaç

I tried the built-in middleware; it works smoothly in this scenario. I use django-rest-framework-simplejwt as the JSON Web Token authentication. We are probably using the same infrastructure.

Thread Thread
 
mrnaif2018 profile image
MrNaif2018

Yeah, same for now, at least both APIs are in python (;
But I am migrating from JWT for good, will write an article why, at least for now it seems better.

Collapse
 
peterhuppertz profile image
peterhuppertz

Thanks for this great post. I implemented it in my application. I have one operation that invalidates the access_token on the server side. I'm trying around with renewing it using the refresh_token, however not really successful for now. Do you have an idea how to implement a check, if the access_token is still valid and if not to refresh it using the refresh_token?

Collapse
 
mrnaif2018 profile image
MrNaif2018

Thank you!
You can see the ~/plugins/auth.js file and function decodeToken in it.
So if token is still valid then you can do like

if (decodeToken(token).exp > 0)
// valid

Basically this piece of code in the plugin does exactly that(also found in ~/plugins/auth.js file):

if (refreshInterval < 0) {
refreshInterval = (await refreshTokenF($auth, $axios, token, refreshToken) * 1000 - Date.now()) * 0.75
$auth.fetchUserOnce()
}

Collapse
 
ohidere profile image
ohidere

Dude you have no idea how long I've been trying to solve this as a frontend newbie.
Really appreciate sharing the code <3

Collapse
 
mrnaif2018 profile image
MrNaif2018

Thanks! Together we solved it (:

Collapse
 
chhumsina profile image
CHHUM Sina

Thank you for sharing :)

Collapse
 
jjspscl profile image
Joshua John Pascual • Edited

For anybody here using django-simpleJWT and having trouble with the Refresh token. I'd like to share my solution by modifying lines

  //  auth.js
  const response = await $axios.post('token/refresh/',
          {
            'refresh': refreshToken
          }
        )

  token = 'Bearer ' + response.data.access
  refreshToken = response.data.refresh
Enter fullscreen mode Exit fullscreen mode

and make sure have the following settings in Django

  ### settings.py
  SIMPLE_JWT = {
      'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
      'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
      'ROTATE_REFRESH_TOKENS': True, # IMPORTANT
      'BLACKLIST_AFTER_ROTATION': True # IMPORTANT
  }
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mrnaif2018 profile image
MrNaif2018

Thanks for sharing that! I was using my custom JWT implementation so responses differ a little bit of course!

Collapse
 
davidchuka profile image
David Chuka • Edited

@mrnaif2018 thanks a lot for this tutorial. It saved me a lot of time. @jjspscl Hi, thanks for the update on using django-simpleJWT (that's what my project uses) I've been trying to register a new user but I've been getting a 403 forbidden error on my register endpoint, meanwhile my login works fine. Any idea what could be wrong?

Collapse
 
jjspscl profile image
Joshua John Pascual • Edited

Seems of out topic, it would be best if you'd Chat me

Thread Thread
 
davidchuka profile image
David Chuka

Okay please follow back so I can access you via dm 🙏🏿 thanks

Collapse
 
icangku profile image
icang

how to create @nuxt/auth google can be validated with jwt in my server api

Collapse
 
mrnaif2018 profile image
MrNaif2018

Hmm, sorry? Could you provide more context please? If it is related to google oauth, nuxt auth module has oauth provider built-in.

Collapse
 
ohidere profile image
ohidere

Hi MrNaif

I have a question I really need help with. I thought you would be the one able to help me as you created this amazing plugin.

When trying to access a page with auth middleware, everything works correctly. The problem is, though, that the application asynchronously loads and sees if the token is valid.
This causes 2 redirects that don't look very good for the user experience. How can I make it so that the token gets check SYNCHRONOUSLY and nothing is being shown on the page, and THEN perform redirects whenever the user is not authenticated?

Please see a GIF of the issue here:
i.imgur.com/ZSx5ByF.gif

(PS: Page not found notification is because I haven't defined the login route yet.)

Collapse
 
mrnaif2018 profile image
MrNaif2018

Hello! I think that for the best UX redirects should be done server side, via a middleware. Maybe try adding a middleware which checks token, and redirects based on that. Also those redirects look a little bit like hydration issues, check if you have those.

Collapse
 
ohidere profile image
ohidere

Thanks for your reply!
Do you mind giving out your contact details? I don't really know what you mean.

There'll be a little reward too ^^

Thread Thread
 
mrnaif2018 profile image
MrNaif2018

Sure, as long as it is something simple (:
Contact me in telegram(@MrNaif_bel)