Nuxt 3 JWT authentication using $fetch and Pinia

i’m gonna share everything, even the parts you marked as done, for completeness sake.

Firstly, you will need something to generate a JWT in the backend, you can do that plainly without any packages, but i would recommend this package for that. Also i’ll use objection.js for querying the database, should be easy to understand even if you don’t know objection.js

Your login view needs to send a request for the login attempt like this

const token = await $fetch('/api/login', {
    method: 'post',
    body: {
      username: this.username,
      password: this.password,
    },
  });

in my case it requests login.post.ts in /server/api/

import jwt from 'jsonwebtoken';
import { User } from '../models';

export default defineEventHandler(async (event) => {
  const body = await useBody(event);
  const { id } = await User.query().findOne('username', body.username);
  const token: string = await jwt.sign({ id }, 'mysecrettoken');
  return token;
});

For the sake of simplicity i didn’t query for a password here, this depends on how you generate a user password.
‘mysecrettoken’ is a token that your users should never get to know, because they could login as everybody else. of course this string can be any string you want, the longer the better.

now your user gets a token as the response, should just be a simple string. i’ll write later on how to save this one for future requests.

To make authenticated requests with this token you will need to do requests like this:

$fetch('/api/getauthuser', {
            method: 'post',
            headers: {
              authentication: myJsonWebToken,
            },
          });

i prefer to add a middleware for accessing the authenticated user in my api endpoints easier. this middleware is named setAuth.ts and is inside the server/middleware folder. it looks like this:

import jwt from 'jsonwebtoken';

    export default defineEventHandler(async (event) => {
      if (event.req.headers.authentication) {
        event.context.auth = { id: await jwt.verify(event.req.headers.authentication, 'mysecrettoken').id };
      }
    });

What this does is verify that if an authentication header was passed, it checks if the token is valid (with the same secret token you signed the jwt with) and if it is valid, add the userId to the request context for easier endpoint access.

now, in my server/api/getauthuser.ts endpoint in can get the auth user like this

import { User } from '../models';

export default defineEventHandler(async (event) => {
  return await User.query().findById(event.context.auth.id)
});

since users can’t set the requests context, you can be sure your middleware set this auth.id

you have your basic authentication now.

The token we generated has unlimited lifetime, this might not be a good idea. if this token gets exposed to other people, they have your login indefinitely, explaining further would be out of the scope of this answer tho.

you can save your auth token in the localStorage to access it again on the next pageload. some people consider this a bad practice and prefer cookies to store this. i’ll keep it simple and use the localStorage tho.

now for the part that users shouldnt access pages other than login: i set a global middleware in middleware/auth.global.ts (you can also do one that isnt global and specify it for specific pages)
auth.global.ts looks like this:

import { useAuthStore } from '../stores';

export default defineNuxtRouteMiddleware(async (to) => {
  const authStore = useAuthStore();

  if (to.name !== 'Login' && !localStorage.getItem('auth-token')) {
    return navigateTo('/login');
  } else if (to.name !== 'Login' && !authStore.user) {
    authStore.setAuthUser(await $fetch('/api/getauthuser', {
      headers: authHeader,
    }));
  }
});

I’m using pinia to store the auth user in my authStore, but only if the localstorage has an auth-token (jwt) in it. if it has one and it hasnt been fetched yet, fetch the auth user through the getauthuser endpoint. if it doesnt have an authtoken and the page is not the login page, redirect the user to it

Leave a Comment