Authentication in Nuxt app Part 2 Last update
Last update
Auth module and Express.js implementation

- Changing the existing User model
- Set up an email provider
- Create an email controller
- Create verification tokens using Crypto and JWT
- Verify tokens - create vue and controller
- Resend verification tokens
- Block unverified users
To follow this post you should first read Authentication in Nuxt part one.
In this post I cover how you can add email verification to your existing authentication system proposed in the part one based on nuxt.js auth module, express.js and password.js. I also show you how to implement password changing and resetting.
Changing the existing User model
First change a little bit the user model by adding 4 new properties. You will need the following fields:
- verifiactionToken - to format unique url for the user so he can prove access to the provided email account
- verifiactionTokenExpire - to restrict access to the url for a only specified time span
- isVerified - to save the results of verification
- resetPassword - to indicate that a user want to reset the password
//api/models/user.js
const UserSchema = new Schema({
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
verificationToken: { type: String, required: true},
verificationTokenExpire: { type: Date, required: true },
isVerified: { type: Boolean, required: true, default: false},
resetPassword: { type: Boolean, required: true, default: false}
})
Set up an email provider
You will send generated tokens nested in urls to the users but don't use SMPT config of your email. The better way is to go for professional email delivery services like SendGrid or MailGun. They both provide convenient API's and reasonable prices. SandGrid is totally free up to 100 emails per day (forever!) That's why I will continue this post based on it as it should be enough for testing purposes and even for many production web apps as well.
The registration process is easy after setting up your account and creating a single sender

Then follow the steps for Node.js environment in the API web integration guide (Email API tab). Don't accept verification yet when asked. First check if it works in your app!

Create an email controller
Create a new controller for managing emails sending process.
//api/controllers/mailer.controller.js
const sgMail = require('@sendgrid/mail')
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
const from = 'dev@blowstack.com'
async function SendEmail (to, subject, text, html) {
const msg = {
to,
from,
subject,
text,
html
}
await sgMail
.send(msg)
.then(() => {
console.log('Email sent')
})
.catch((error) => {
console.error(error)
})
}
export default {
SendEmail
}
You should then test your newly implemented API. The good idea is to send a message every time somebody is registering in your app. Import the controller into the authentication.js file and send a message with information about successful registration.
//api/routes/authentication.js
...
import MailerController from '../controllers/mailer.controller'
...
router.post('/auth/register', async (req, res) => {
...
await AuthenticationController.CreateUser(email, hashedPassword)
.then(async () => {
await MailerController.SendEmail(
email
'Registration confirmation',
'You has been registered correctly',
'<p>You has been registered correctly</p>'
)
res.send({ status: 'ok' })
}).catch((err) => {
throw err
})
})
If everything works as expected remember to accept the implementation in SendGrid!
Create verification tokens using Crypto and JWT
Knowing that the email service works and the user model is properly adjusted you can start creating verification tokens and send them after the registration process. I noticed that many developers use a not maintained randomstring package to generate hashes. Don't do that! Use up to date build-in node.js package - Crypto.
To continue you should implement 4 new functions responsible for:
- generating a verification token - random long string based
- generating a verification token expire time
- verifying signed verification token - verification of JWT string
- signing a verification token - to alter token into JWT
Additionally add constant with a default expiration time. The presented one results in one day span. I also used a different secret when generating user password JWT string.
//api/controllers/authentication.controller
...
const crypto = require('crypto');
const expireSpan = 3600 * 1000 * 24
const authEmailVerificationSecret = process.env.AUTH_EMAIL_VERIFICATION_SECRET
async function CreateUser (email: string, password: string) {
const verificationToken = generateVerificationToken()
const verificationTokenExpire = generateVerificationTokenExpire()
return await User.create({ email, password, verificationToken, verificationTokenExpire })
.then((data) => {
return data
}).catch((error: Error) => {
throw error
})
}
function generateVerificationToken () {
return crypto.randomBytes(30).toString('hex')
}
function generateVerificationTokenExpire () {
return new Date(Date.now() + expireSpan)
}
function signVerificationToken (email, verificationToken) {
return jwt.sign({
email,
verificationToken
}, authEmailVerificationSecret)
}
function verifySignedVerificationToken (token) {
return jwt.verify(token, authEmailVerificationSecret)
}
export default {
...
generateVerificationToken,
generateVerificationTokenExpire,
signVerificationToken
verifySignedVerificationToken
...
}
You can save a token in the database but you still need to send it to a newly created user as well. First add a new method to the mailer controller - SendRegistrationToken it should be responsible for sending verification token messages. Create an auxiliary method which will strip our template from html tags so we can pass it at once as a simple text to the SendEmail method. To make things more secure and performant use JWT to couple an user email and a token. This way you won't disclose a real token to a user and you will be able to make a fast query to the database when a user clicks a validation link.
// api/controllers/mailer.controller.js
function stripHtmlTags (html) {
return html.replace(/(<([^>]+)>)/gi, "");
}
async function SendRegistrationToken (to, subject, verificationToken) {
const html = `<p>Please click into the link below to confirm your email address and finish the registration</p><a href="http://localhost:3000/register/confirmation?token=${verificationToken}">Active your account</a>`
const text = stripHtmlTags(html)
await SendEmail(to, subject, text, html)
}
Now implement SendRegistrationToken in the authentication.js file in the routes folder. Notice that this time you have to use the user variable returned form the Promise to get our token. Remember to couple an email and a token into JWT. Consider using a different secret than that used for the JWT user password creation. Overall you should get a similar code:
//api/routes/authentication.js
router.post('/auth/register', async (req, res) => {
...
await AuthenticationController.CreateUser(email, hashedPassword)
.then(async (user) => {
const signedVerificationToken = AuthenticationController.signVerificationToken(user.email, user.verificationToken)
await MailerController.SendRegistrationToken(
email,
'Registration confirmation',
signedVerificationToken
)
res.send({ message: 'We send an email with verification link. Check your inbox!' })
}).catch((err) => {
throw err
})
})
Test if it sends a token during registration. For now when a user clicks the link it goes nowhere. We should implement the logic for the verification of a token and a proper view in nuxt.js.
Verify tokens - create vue and controller
Create a view where the user will be redirected after clicking the verification link. In the register folder create a folder named confirmation and then a new index.vue file. So when a user is redirected to this page a verification process will be initiated through a mounted cycle. In case of a successful verification you can display a button to login. On the other hand if a user can't be verified you can display a button for resending a verification link.
// pages/register/confirmation/index.vue
<template>
<div class="mt-12">
<v-container>
<v-row justify="center" class="mt-12 text-center">
<v-col cols="12" v-if="confirmationStatus === 'verified'">
<v-btn to="/login" class="ma-3" color="primary">Go to login page</v-btn>
</v-col>
<v-col cols="12" v-else-if="confirmationStatus === 'unverified'">
<v-btn to="/register/confirmation/resend" class="ma-3" color="primary">Resend a verification token</v-btn>
</v-col>
</v-row>
</v-container>
<snack-bar :snackbar-message="snackbarMessage"></snack-bar>
</div>
</template>
<script>
import SnackBar from '@/components/snackBar'
export default {
name: 'index',
components: { SnackBar },
data: () => ({
resend: false,
confirmationStatus: false,
snackbarMessage: ''
}),
methods: {
async checkToken () {
const token = this.$route.query.token
const verification = await this.$axios.post('/api/auth/confirmation/', {
token
})
this.confirmationStatus = verification.data.confirmationStatus
this.snackbarMessage = verification.data.message
}
},
mounted () {
this.checkToken()
}
}
</script>
Time for the backend. Create a new route in the authentication.js file which will be responsible for the verification process. In the body write a code that gets a token from the params and extracts the email and verification token from it. Find a user based on the extracted email. There are only two scenarios you should take into consideration:
- an user with the email has been found, token and expiration span in valid
- any other scenario not fulfilling the all above conditions
// api/routes/authentiaction.js
router.post('/auth/confirmation', async (req, res) => {
const token = req.body.token
const { email, verificationToken } = AuthenticationController.verifySignedVerificationToken(token)
const user = await AuthenticationController.GetUser(email)
if (user &amp;&amp; user.verificationToken === verificationToken &amp;&amp; user.verificationTokenExpire >= new Date()) {
user.isVerified = true
user.save()
return res.send({
confirmationStatus: 'verified',
message: 'Your email has been verified.'
})
} else {
return res.send({
confirmationStatus: 'unverified',
message: 'Email can\'t be verified!\n. The possible reason is expired token.'
})
}
})
Resend verification tokens
There is still one more important thing missing. Namely there are no verification email resend options available for expired or not delivered tokens. Let's fix it now!
Create a new route in authentication.js file with the following code. This should do the trick. It simply finds a user by the email and generates for him a new token and an expiration span. Finally sends the new token in an email. Now you have to add a new vue to give a user possibility to reach this endpoint.
// api/routes/authentication.js
router.post('/auth/confirmation/resend', async (req, res) => {
const email = req.body.email
const user = await AuthenticationController.GetUser(email)
if (user &amp;&amp; user.isVerified === true) {
return res.send('All ready verified.')
} else if (user) {
const verificationToken = AuthenticationController.generateVerificationToken()
const verificationTokenExpire = AuthenticationController.generateVerificationTokenExpire()
user.verificationToken = verificationToken
user.verificationTokenExpire = verificationTokenExpire
user.save()
const signedVerificationToken = AuthenticationController.signVerificationToken(user.email, user.verificationToken)
await MailerController.SendRegistrationToken(user.email, 'Registration confirmation - resend', signedVerificationToken)
return res.send('Token has been resent.')
} else {
return res.send('Token can\'t be resent.')
}
})
Create a form with only one input - email and two props: button title and email which can be used in the resending vue.
// forms/emailForm.vue
<template>
<v-form
ref="form"
v-model="form.valid"
lazy-validation
class="div-center"
>
<v-text-field
v-model="form.email"
:rules="emailRules"
validate-on-blur
label="E-mail"
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: 'emailForm',
data: () => ({
emailRules: [
v => !!v || 'E-mail is required',
v => /.+@.+\..+/.test(v) || 'E-mail must be valid'
]
}),
props: {
buttonTitle: {
required: true
},
form: {
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>
Use this form in a new vue responsible for resending a token. You could create it in a new folder named resend in the register folder
// pages/register/resend/index.vue
<template>
<div class="mt-12">
<v-container class="text-center">
<v-row class="mt-12 text-center" justify="center">
<h2>Verification token resending</h2>
</v-row>
<v-row :align="'center'"
:justify="'center'" class="mt-12 ">
<v-col cols="12" md="6" lg="3">
<email-form button-title="Resend verification token" :form.sync="form"></email-form>
</v-col>
</v-row>
</v-container>
<snack-bar :snackbar-message.sync="snackbarMessage"></snack-bar>
</div>
</template>
<script>
import EmailForm from '@/forms/emailForm'
import SnackBar from '@/components/snackBar'
export default {
components: {
SnackBar,
EmailForm
},
data: () => ({
snackbar: false,
snackbarMessage: '',
form: { finish: false }
}),
computed: {
finish () {
return this.form.finish
}
},
watch: {
finish (newVal) {
if (newVal) {
this.resendToken(this.form.email)
this.form.finish = false
}
}
},
methods: {
async resendToken (email) {
const resendVerification = await this.$axios.post('/api/auth/confirmation/resend', {
email
})
this.snackbarMessage = resendVerification.data
this.snackbar = true
}
}
}
</script>
Block unverified users
You have basically a ready solution for verifying users' emails. All works with resending options included. But still it doesn't matter for your authentication process because you haven't blocked unverified user access yet. It's super easy so let's add this blockade now. Go to the authentication controller, find the code responsible for LocalStrategy used by passport.js.
All you have to do is to add another check this time for isVertified field and in case of fail give a proper message. It could look like this:
// api/routes/authentication.js
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 &amp;&amp; user.isVerified) {
return done(null, user)
} else if (validation) {
return done(null, false, {
message: 'You have to verify you email address',
resendToken: true
})
} else {
return done(null, false, { message: 'Authentication failed' })
}
}).catch((err) => {
return done(err)
})
}
)
)
Ok, users with not verified emails are blocked. Almost everything done. Let's finish it up and implement password changing and resetting.
Resetting and changing password
The both functions can be combined into one. When a user wants to change a password an email with a token is sent. Clicking a link an user is redirected to the form with possibility to input a new password. To implement this you need more endpoints. One responsible for sending resetting tokens and the other responsible for verifying the token from the url and accepting a new password. Add also a new method in the mailer controller responsible for sending resetting tokens.
// api/routes/authentication.js
...
router.post('/auth/password/reset', async (req, res) => {
const email = req.body.email
const user = await AuthenticationController.GetUser(email)
if (user) {
const verificationToken = AuthenticationController.generateVerificationToken()
const verificationTokenExpire = AuthenticationController.generateVerificationTokenExpire()
user.verificationToken = verificationToken
user.verificationTokenExpire = verificationTokenExpire
user.passwordReset = true
user.save()
const signedVerificationToken = AuthenticationController.signVerificationToken(user.email, user.verificationToken)
await MailerController.SendPasswordChangeToken(user.email, 'Password resetting', signedVerificationToken)
return res.send({ message: 'Link has been sent. Check you email.' })
} else {
return res.send({ message: 'Password can\'t be renew' })
}
})
router.post('/auth/password/change', async (req, res) => {
const token = req.body.token
const password = req.body.password
const { email, verificationToken } = AuthenticationController.verifySignedVerificationToken(token)
const user = await AuthenticationController.GetUser(email)
if (user &amp;&amp; user.verificationToken === verificationToken &amp;&amp; user.verificationTokenExpire >= new Date() &amp;&amp; user.passwordReset === true) {
try {
user.password = await AuthenticationController.generatePasswordHash(password)
user.passwordReset = false
user.save()
return res.send({ message: 'Password has been changed' })
} catch (err) {
return res.send({ message: 'Some error happened. Contact administrator' })
}
} else {
return res.send({ message: 'Token is invalid!. Re-send your request' })
}
})
// api.controller/mailer.controller.js
async function SendPasswordChangeToken(to, subject, verificationToken) {
const html = `<p>Please click into the link below to change your password</p><a href="http://localhost:3000/login/reset/confirmation?token=${verificationToken}">Change your password</a>`
const text = stripHtmlTags(html)
await SendEmail(to, subject, text, html)
}
export default {
...
SendPasswordChangeToken
}
You have ready backend. Now you have to only create a new form with double password inputs and two new vues. One view will be responsible for the possibility to send a resetting token and the other to input a new password. Let's start with the vue that give an user ability to send a request for password change.
It can be placed in pages/reset folder.
// pages/login/reset/index.vue
<template>
<div class="mt-12">
<v-cntainer>
<v-row justify="center" class="mt-12">
<v-col cols="12" lg=3 class="mt-12 text-center">
<email-form button-title="Change/reset password" :form.sync="form"></email-form>
</v-col>
</v-row>
</v-cntainer>
<snack-bar :snackbar-message="snackbarMessage"></snack-bar>
</div>
</template>
<script>
import EmailForm from '@/forms/emailForm'
import SnackBar from '@/components/snackBar'
export default {
name: 'index',
components: {
SnackBar,
EmailForm
},
data: () => ({
form: { finish: false },
snackbarMessage: ''
}),
computed: {
finish () {
return this.form.finish
}
},
watch: {
finish (newVal) {
if (newVal) {
this.sendPasswordToken()
this.form.finish = false
}
}
},
methods: {
async sendPasswordToken () {
try {
const email = this.form.email
const response = await this.$axios.post('/api/auth/password/reset', {
email
})
this.snackbarMessage = response.data.message
} catch (err) {
this.snackbarMessage = err.response.data.message
}
}
}
}
</script>
You can now test if a request for resetting / changing password works. Then start creating the last form for changing password and implement it in a proper vue.
// forms/changePasswordForm.vue
<template>
<v-form
ref="form"
v-model="form.valid"
lazy-validation
>
<v-text-field
v-model="form.password"
:counter="20"
:rules="[(form.password === form.passwordCheck) || 'Passwords must match', ...passwordRules]"
:type="'password'"
label="Password"
required
></v-text-field>
<v-text-field
v-model="form.passwordCheck"
:rules="[(form.password === form.passwordCheck) || 'Passwords must match', ...passwordRules]"
:counter="20"
: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: 'changePasswordForm',
data: () => ({
passwordRules: [
v => !!v || 'Password is required',
v => (v &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 implement the form in a new vue which can be placed in pages/login/reset folder.
// pages/login/reset/confirmation/index.vue
<template>
<div class="mt-12">
<v-container>
<v-row justify="center" class="mt-12">
<h3>Change your password</h3>
</v-row>
<v-row justify="center" class="mt-12">
<change-password-form button-title="Change password" :form.sync="form"></change-password-form>
</v-row>
</v-container>
<snack-bar :snackbar-message="snackbarMessage"></snack-bar>
</div>
</template>
<script>
import ChangePasswordForm from '@/forms/changePasswordForm'
import SnackBar from '@/components/snackBar'
export default {
name: 'index',
components: {
SnackBar,
ChangePasswordForm
},
data: () => ({
form: { finish: false },
token: '',
snackbarMessage: ''
}),
computed: {
finish () {
return this.form.finish
}
},
watch: {
finish (newVal) {
if (newVal) {
this.changePassword()
this.form.finish = false
}
}
},
mounted () {
this.token = this.$route.query.token
},
methods: {
async changePassword () {
const token = this.token
try {
const verification = await this.$axios.post('/api/auth/password/change', {
token,
password: this.form.password
})
this.snackbarMessage = verification.data.message
} catch (err) {
this.snackbarMessage = err.response.data.message
}
}
}
}
</script>
That's all. You now have a working authentication system in Nuxt.js based on JWT. The next part will be focused on ACL.