blowstack logo
  • BlowStack
  • ->
  • Blog
  • ->
  • Authentication in Nuxt app - Auth module and Express.js implementation

Authentication in Nuxt app

Auth module and Express.js implementation

Last update

25 min.
  1. Implementing auth module.
  2. Create endpoints.
  3. Create a User model and a authentication controller.
  4. Encrypt users passwords.
  5. Logging using JWT.
  6. Create the user endpoint.
  7. Block endpoints.

 

There are many tutorials how to set up authentication in Nuxt using auth module but I didn't come across any that covers both backend and frontend. This post covers all the aspects of Nuxt authentication so you can easily follow it step by step to restrict access to your app. This is the first part which is focused on basic authentication. Next parts are focused on email verification, password resetting and granular app accessing using scope.

 

Implementing auth module

 

This step is well covered on official website for Nuxt.js auth module. Anyway I present here a condensed form for quick implementation if you don't have time to watch almost 45 minutes video.

 

First install the official authentication module for Nuxt.

npm i @nuxtjs/auth

 

After installation add auth module to the nuxt config. Notice that this module requires axios to work so if you don't have it, install it now and add to the modules section as well.

//nuxt.config.js

...
modules: [
  '@nuxtjs/axios',
  '@nuxtjs/auth'
],
...

 

Then create login and register folders in the page folder of your app and place vues with forms into them. As there is already example of this pages on the official auth module website I present here Vuetify version of them. If you are not using Vuetify feel free to implement basic official version as it can't impact authentication process. The most important is that you will create form for user password and email input and send them by axios using auth module to specific endpoints that I will cover later.

 

For logging use:

let user = await this.$auth.loginWith('local', {
          data: {
            email: this.email,
            password: this.password
          },
        })

 

And for registering use:

await this.$axios.post('/api/auth/register', {
   email: this.email,
   password: this.password
 });

let user =  await this.$auth.loginWith('local', {
   data: {
     email: this.email,
     password: this.password
   },
 });

 

 

Vuetify version

Create a common form that can be reused for both: logging and registering as they use the same input fields. You could name it authentiactionForm.vue and put it into the forms folder (it's not a default vue/nuxt folder, you will have to create it). In the body insert basic form that can collects and validates an user email and password. Use props to pass two crucial variables: form which collects all the data and button title responsbile for proper title for the submit button. Using both props and $emit you can easily send back data to the parent component.

// form/authenticationForm.vue

<template>
  <v-form
    ref="form"
    v-model="form.valid"
    lazy-validation
  >
    <v-text-field
      v-model="form.email"
      :rules="emailRules"
      label="E-mail"
      required
    ></v-text-field>

    <v-text-field
      v-model="form.password"
      :counter="20"
      :rules="passwordRules"
      :type="'password'"
      label="Password"
      required
    ></v-text-field>
    <v-btn
      :disabled="!form.valid"
      color="indigo lighten-1"
      class="mr-4"
      @click="validate"
    >
      {{ buttonTitle }}
    </v-btn>
  </v-form>
</template>

<script>
export default {
  name: 'authenticationForm',
  data: () => ({
    emailRules: [
      v => !!v || 'E-mail is required',
      v => /.+@.+\..+/.test(v) || 'E-mail must be valid',
    ],
    passwordRules: [
      v => !!v || 'Password is required',
      v => (v &amp;amp;amp;amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp;amp;amp;amp; v.length <= 20) || 'Password must be less than 20 characters',
    ]
  }),
  props: {
    form: {
      required: true,
    },
    buttonTitle: {
      required: true
    }
  },
  mounted () {
  this.form.valid = false
    },
  methods: {
    validate () {
      if (this.$refs.form.validate()) {
        this.form.finish = true
        this.$emit('update:form', this.form)
      }
    }
  }
}
</script>

 

Then create a login page using just created form. You can put it into a new folder named login in the pages folder. Import the authenticationForm component and fill props with form object and a proper title. Watch changes made to the form, if a user finishes entering valid data then login method sends this data to the proper endpoint (not exists yet but soon I will cover it). Additionally create a notifying component (SnackBar) for feedback presented to an user (you will find it below).

// pages/login/index.vue

<template>
    <v-container class="text-center">
      <v-row :align="'center'"
             :justify="'center'" class="mt-12">
        <v-col cols="12" md="6" lg="3">
          <authentication-form button-title="Logging" :form.sync="form"></authentication-form>
        </v-col>
      </v-row>
      <snack-bar :snackbar-message.sync="snackbarMessage"></snack-bar>
    </v-container>
</template>

<script>

import AuthenticationForm from '@/forms/authenticationForm'
import SnackBar from '@/components/snackBar'

export default {
  components: { AuthenticationForm, SnackBar },
  data: () => ({
    form: {
      valid: false,
      email: '',
      password: '',
      finish: false
    },
    snackbarMessage: '',
  }),
  computed: {
    finish () {
      return this.form.finish
    }
  },
  watch: {
    finish (newVal) {
      if (newVal) {
        this.login()
        this.form.finish = false
      }
    }
  },
  methods: {
    async login () {
      try {
       const response = await this.$auth.loginWith('local', {
          data: {
            email: this.form.email,
            password: this.form.password
          }
        })
        this.snackbarMessage = response.data.message
      } catch (error) {
        this.snackbar = true
        this.snackbarMessage = error.response.data.message
      }
    }
  }
}
</script>

 

And the Snackbar code. The Snackbar will be shown whenever a passed snackbarMessage will be changed but not an empty string because it's an initial state where you don't want to show any message.

// components/snackBar.vue

<template>
  <v-snackbar
    v-model="snackbar"
    :top="true"
    :color="'error'"
    :timeout="5000"
  >
    {{ snackbarMessage }}
    <v-btn
      dark
      text
      @click="snackbar = false"
      title="close"
    >
      Close
    </v-btn>
  </v-snackbar>
</template>

<script>
export default {
  name: 'snackBar',
  data: () => ({
    snackbar: false
  }),
  props: {
    snackbarMessage: {
      required: true
    }
  },
  watch: {
    snackbarMessage (val) {
      if (val.length > 1) {
        this.snackbar = true
      }
    },
    snackbar (newVal) {
      if (newVal === false) {
        this.$emit('update:snackbarMessage', '')
      }
    }
  }
}
</script>

 

Registration is very similar. Use the same form and snackBar component but change login method into register, you will the the difference from the code below.

// pages/register/index.vue

<template>
  <v-container class="text-center">
    <v-row :align="'center'"
           :justify="'center'" class="mt-12">
      <v-col cols="12" md="6" lg="3">
        <authentication-form button-title="Register" form.sync="form"></authentication-form>
      </v-col>
    </v-row>
    <snack-bar :snackbar-message.sync="snackbarMessage"></snack-bar>
  </v-container>
</template>
<script>
import AuthenticationForm from '@/forms/authenticationForm'
import SnackBar from '@/components/snackBar'

export default {
  components: { AuthenticationForm, SnackBar },
  data: () => ({
    form: {
      valid: false,
      email: '',
      password: '',
      finish: false
    },
    snackbarMessage: ''
  }),
  computed: {
    finish () {
      return this.form.finish
    }
  },
  watch: {
    finish (newVal) {
      if (newVal) {
        this.register()
        this.form.finish = false
      }
    }
  },
  methods: {
    async register () {
      try {
        await this.$axios.post('/api/auth/register', {
          email: this.form.email,
          password: this.form.password
        })

        const user = await this.$auth.loginWith('local', {
          data: {
            email: this.form.email,
            password: this.form.password
          }
        })

        if (user) {
          await this.$router.push('/admin')
        }

      } catch (error) {
        this.snackbarMessage = error.response.data.message
      }
    }
  }
}
</script>

 

TIme to config the auth module. Do this in the nuxt config file. The most important are endpoints urls which you are going to set up in the backend soon. Copy and paste the code below and change urls if necessary.

//nuxt.config.js

...
auth: {
  localStorage: true,
  strategies: {
    local: {
      endpoints: {
        login: {
          url: '/api/auth/login',
          method: 'post',
          propertyName: 'token'
        },
        logout: false,
        user: {
          url: '/api/auth/user',
          method: 'get',
          propertyName: false
        },
      },
    }
  },
  redirect: {
    logout: '/',
    callback: '/login',
    home: '/'
  },
},
...

 

The last thing to do on the frontend is to choose pages that will be guarded by the auth module. Add auth as a middleware to every page you want to secure. If you have multiple pages of that type, the best solution will be to create a new layout and add middleware to it.

<script>
export default {
  components: { },
  middleware: ['auth']
}
</script

 

You are ready to proceed to the backend, where all the authentication takes place. The just covered auth module doesn't do it for you. It's just help you to manage tokens, make internal api calls to the backend endpoints and restrict access to some of your pages but token generation, validation, registration etc has to be handled by the backend. Let's do this!

 

Create endpoints

 

Start with the two enpoints declared in the nuxt config for login and user.

// api/routes/authentication.js
...
router.post('/auth/login', async (req, res) => {
})

router.get('/auth/user', async (req, res) => {
  res.send({ ok: 'ok' })
})

 

One important endpoint is lacking. Namely the register one which is not declared in the next config but you used it in the register vue. Let's fix it.

// api/routes/authentication.js
...
router.post('/auth/register', async (req, res) => {
})

 

You fill the endpoints with logic soon but now let's create a User model and the controller which help you make your code consistent and clear.

 

Create a User model and a authentication controller

 

I am a fan of mongoose but if you use different ODM or just plain mongodb connector adjust this file to your preferences.

// api/models/user.js

const mongoose = require('mongoose')
const Schema = mongoose.Schema

const UserSchema = new Schema({
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true }
})

export default mongoose.model('User', UserSchema)

 

In the controller there are initially only two functions. We want to create a new user and get the user. That's all for now.

// api/controllers/authentication.controller.js

import User from '../models/user'

async function CreateUser (email, password) {
  return await User.create({ email, password })
    .then((data) => {
      return data
    }).catch((error: Error) => {
      throw error
    })
}

async function GetUser (email) {
  return await User.findOne({ email })
    .then((data) => {
      return data
    }).catch((error: Error) => {
      throw error
    })
}

export default {
  CreateUser,
  GetUser
}

 

 

Encrypt users passwords

 

During registration you will send password to the register endpoint. It's a good practice to encyrpt your password before storing it in the database. I strongly encourage you to use Bcrypt which gives you far more better security than SHA256, SHA512 or MD5. First install the Bcrypt for node.js.

npm i bcrypt

 

Then require it in the authentication controller and create a new method based on it responsible for hashing passwords. 

// api/controllers/authentication.controller.js

...
const bcrypt = require('bcrypt')
...

async function generatePasswordHash (plainPassword) {
  return await bcrypt.hash(plainPassword, 12)
}

...
export default {
...
  generatePasswordHash,
...
}

 

Finally add a lacking logic to register enpoint so registering a new user will be possible.

// api/routes/authentication.js

import AuthenticationController from '../controllers/authentication.controller'
const { Router } = require('express')
const router = Router()

router.post('/auth/register', async (req, res) => {

  const password = req.body.password
  const email = req.body.email
  const hashedPassword = await AuthenticationController.generatePasswordHash(password)

  await AuthenticationController.CreateUser(email, hashedPassword)

    .then(() => {
      res.send({ message: 'An account has been created!' })
    }).catch((err) => {
      throw err
    })
})

 

That's all for registration process. Test your register form and look up the databse for the newly created user. If it's inserted poperly you can proceed to the login enpoint.

 

Logging using JWT

 

First few words about how the auth module with JWT for Nuxt works. The auth module enables storing token taken from the backend. If it encounter middleware auth in the vue file, the user endpoint is called using axios where password and other user data is extracted from the JWT token and compared with the data saved in the database.

Currently the logic for the user endpoint is totally blank but the idea is to check if the stored token in the auth module is valid using the backend. That's why it's convinient to pass around JWT which can include multiple data user in an encrypted way. Notice how important is the user endpoint, it validates correctness of the token when a user visits restricted pages. If you don't implement correctly this endpoint, an intruder can pass through your auth module easily without logging at all! It can happen because the nuxt auth module expects the logging endpoint just to return a token, any token, so it won't check if it's valid. Reponsible for checking is the user endpoint where the token validation process is done and this endpoint is called everytime the auth middleware is encountered.

Now that you know how authentication in the Nuxt works we can implement logging and user endpoints using JWT. It's not very complicated but I recommend using tools that are constantly maintained like Password.js. Let's install and implement it so it will work seemlessly with the nuxt auth module.

 

npm install passport passport-jwt passport-local jsonwebtoken

 

In the authentication controller add local strategy pattern for logging. Moreover add a new method which can be able to compare user inputed plain password with the hashed one saved in the database. Add another method that can changed user data into JWT and a const holding long string as a secret for generating JWT string (consider using env here).

// api/controllers/authentication.controller.js

...
const passport = require('passport')
const LocalStrategy = require('passport-local').Strategy
const authUserSecret = process.env.AUTH_USER_SECRET // an arbitrary long string, you can ommit env of course

passport.use(
  new LocalStrategy(
    {
      usernameField: 'email',
      passwordField: 'password'
    },
    async function (email, password, done) {
      await GetUser(email)
        .then((user) => {
          return user
        }).then(async (user) => {
          if (!user) {
            return done(null, false, { message: 'Authentication failed' })
          }
          const validation = await comparePasswords(password, user.password)
          if (validation) {
            return done(null, user)
          } else {
            return done(null, false, { message: 'Authentication failed' })
          }
        }).catch((err) => {
          return done(err)
        })
    }
  )
)

async function comparePasswords (plainPassword, hashedPassword) {
  return await bcrypt.compare(plainPassword, hashedPassword)
}

function signUserToken (user) {
  return jwt.sign({
    id: user.id,
    email: user.email
  }, authUserSecret)
}

 

Then implement the local strategy in your login route.

// api/routes/authentication.js
...
const passport = require('passport')
...
router.post('/auth/login', (req, res) => {
  passport.authenticate('local', { session: false }, (err, user, message) => {
    if (err) {
      // you should log it
      return res.status(500).send(err)
    } else if (!user) {
      // you should log it
      return res.status(403).send(message)
    } else {
      const token = AuthenticationController.signUserToken(user)
      return res.send({ token })
    }
  })(req, res)
})

 

You have to also initialize passport globaly. Go to the index.js file and add it.

// api/index.js

const passport = require('passport');
app.use(passport.initialize())

 

It's a good idea to check if the token is passed to Nuxt. Try to login on previously registered account. Then find out if in your cookies in auth._token.local variable there is a bearer token string.

 

Create the user endpoint

 

Let's finish the authenication by implementing the user endpoint. First we need to create a JWT strategy and add it to the authentication controller. Moreover we have to create a custom extractor of the token passed from the auth module.

// api/controllers/authentication.controller.js
...
const JwtStrategy = require('passport-jwt').Strategy
...
const tokenExtractor = function (req) {
  let token = null
  if (req.req &amp;amp;&amp;amp; req.req.cookies &amp;amp;&amp;amp; req.req.cookies['auth._token.local']) {
    const rawToken = req.req.cookies['auth._token.local'].toString()
    token = rawToken.slice(rawToken.indexOf(' ') + 1, rawToken.length)
  }
  return token
}

passport.use(new JwtStrategy({
  jwtFromRequest: tokenExtractor,
  secretOrKey: authUserSecret
},
function (jwtPayload, done) {
  return GetUser(jwtPayload.email)
    .then((user) => {
      if (user) {
        return done(null, {
          email: user.email,
        })
      } else {
        return done(null, false, 'Failed')
      }
    })
    .catch((err) => {
      return done(err)
    })
}
))}
))

 

Notice what is return if the user is found. Currently it's only the user's email. In the nuxt when using $auth.user variable you can access exacly this email but maybe you want to pass more information?

 

Then implement the JWT strategy into the user endpoint.

// api/routes/authentication.js

router.get('/auth/user', async (req, res) => {
  // console.log(req.cookies['auth._token.local'])
  passport.authenticate('jwt', { session: false }, (err, user, message) => {
    if (err) {
      // you should log it
      return res.status(400).send(err)
    } else if (!user) {
      // you should log it
      return res.status(403).send({ message })
    } else {
      return res.send({ user })
    }
  })(res, req)
})

 

No try to make a couple of tests to find out if the just implemented authentication system works.

 

Block endpoints

 

You block unauthenticated users from accessing specific vues but you don't block them from accessing specific endpoints yet. You can check yourself using i.e. Postman to get easily any data from your database. Luckily to make an endpoint secure it's sufficient to add password.js with JWT strategy as a middleware. For example if you want to block all urls started with ‘admin’ prefix implement this code:

 

// api/routes.js

const adminPages = require('./routes/admin')
app.use('/admin', passport.authenticate('jwt', { session: false }), adminPages)

 

Unfortunatelly you will have to change a little bit tokenExtractor in the authentication controller. In Express.js there are no req object in the req object as it is in nuxt.js. So make the following changes.

// api/controllers/authentication.js

const tokenExtractor = function (req) {
  let token = null
  if (req.req &amp;&amp; req.req.cookies &amp;&amp; req.req.cookies['auth._token.local']) {
    const rawToken = req.req.cookies['auth._token.local'].toString()
    token = rawToken.slice(rawToken.indexOf(' ') + 1, rawToken.length)
  } else if (req &amp;&amp; req.cookies &amp;&amp; req.cookies['auth._token.local']) {
    const rawToken = req.cookies['auth._token.local'].toString()
    token = rawToken.slice(rawToken.indexOf(' ') + 1, rawToken.length)
  }
    return token
}

 

Finally you can use Postman to test your auth module implementation.


 

What's next?

There are still more to consider… Like email verification, password resetting, granular accessing and fixed number of falty tries (against brutal attacks). Hopefully I cover them in the next parts. Check them out!

Recent posts

BlowStack 2021
Portfolio Cheat
sheets