In this tutorial, I’ll be showing you to implement authentication in Nuxt.js app using the Auth module. For the purpose of this tutorial we’ll be using JWT for authentication.
Below is a quick demo of what we’ll be building in this tutorial.
To save ourselves some time, we’ll clone an API, which I have put together for this tutorial:
$ git clone https://github.com/ammezie/jwt-auth-api.git
Then we install the API dependencies:
$ cd jwt-auth-api
$ npm install
Next, rename .env.example
to .env
and generate an APP_KEY
:
// with the adonis CLI
$ adonis key:generate
// without the adonis CLI
$ node ace key:generate
Once that’s done, let’s run the migrations:
// with the adonis CLI
$ adonis migration:run
// without the adonis CLI
$ node ace migration:run
Before we move on to building the Nuxt.js app, let’s quickly go over the API. The API is built using AdonisJs and it uses JWT (JSON Web Tokens) for authentication. It also uses SQLite.
The API has three endpoints:
The API also already has CORS enabled.
Now, we can start the API:
$ npm start
We should be able to access the API on [http://127.0.0.1:3333/api](http://127.0.0.1:3333/api "http://127.0.0.1:3333/api")
. We’ll leave it running.
Note: Though I’ll be using an API built with AdonisJs for the purpose of this tutorial, you are free to use whatever framework that work best for you.## Creating a Nuxt.js app
For this, we’ll make use of the Vue CLI, so you need to first install the Vue CLI in case you don’t have it installed already:
$ npm install -g vue-cli
Then we can create a Nuxt.js app:
$ vue init nuxt/starter nuxt-auth
Next, we need to install the dependencies:
$ cd nuxt-auth
$ npm install
We can launch the app:
$ npm run dev
The app should be running on [http://localhost:3000](http://localhost:3000 "http://localhost:3000")
.
Now, let’s install the Nuxt.js modules that we’ll be needing for our app. We’ll be using the Nuxt Auth module and the Nuxt Axios module, since auth module makes use of Axios internally:
$ npm install @nuxtjs/auth @nuxtjs/axios --save
Once that’s done, add the code below to nuxt.config.js
:
// nuxt.config.js
modules: [
'@nuxtjs/axios',
'@nuxtjs/auth'
],
Next, we need to set up the modules. Paste the code below into nuxt.config.js
// nuxt.config.js
axios: {
baseURL: 'http://127.0.0.1:3333/api'
},
auth: {
strategies: {
local: {
endpoints: {
login: { url: 'login', method: 'post', propertyName: 'data.token' },
user: { url: 'me', method: 'get', propertyName: 'data' },
logout: false
}
}
}
}
Here, we set the base URL (which is that of our API from earlier on) that Axios will use when making requests. Then we define the authentication endpoints for the local
strategy corresponding to those on our API. On successful authentication, the token will be available in the response as a token
object inside a data
object, hence why we set propertyName
to data.token
. Similarly, the response from the /me
endpoint will be inside a data
object. Lastly, we set logout
to false
since our API doesn’t have an endpoint for logout. We’ll just remove the token from localstorage when a user logs out.
To style our app, we’ll be making use of Bulma. Open nuxt.config.js
and paste the code below within the link
object that is inside the head
object:
// nuxt.config.js
{
rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css'
}
Now, let’s create the Navbar component. Rename AppLogo.vue
inside the components
directory to Navbar.vue
and replace it content with the following:
// components/Navbar.vue
<template>
<nav class="navbar is-light">
<div class="container">
<div class="navbar-brand">
<nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>
<button class="button navbar-burger">
<span></span>
<span></span>
<span></span>
</button>
</div>
<div class="navbar-menu">
<div class="navbar-end">
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
My Account
</a>
<div class="navbar-dropdown">
<nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
<hr class="navbar-divider">
<a class="navbar-item">Logout</a>
</div>
</div>
<nuxt-link class="navbar-item" to="/register">Register</nuxt-link>
<nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>
</div>
</div>
</div>
</nav>
</template>
The Navbar component contains links to login or register, links to view profile or logout.
Next, let’s update the default layout to make use of the Navbar component. Open layouts/default.vue
and replace it content with the following:
// layouts/default.vue
<template>
<div>
<Navbar/>
<nuxt/>
</div>
</template>
<script>
import Navbar from '~/components/Navbar'
export default {
components: {
Navbar
}
}
</script>
Also, let’s update the homepage. Open pages/index.vue
and replace it content with the following:
// pages/index.vue
<template>
<section class="section">
<div class="container">
<h1 class="title">Nuxt Auth</h1>
</div>
</section>
</template>
Our app should now look something similar to below:
Inside the pages
directory, create a new register.vue
file and paste the code below in it:
// pages/register.vue
<template>
<section class="section">
<div class="container">
<div class="columns">
<div class="column is-4 is-offset-4">
<h2 class="title has-text-centered">Register!</h2>
<Notification :message="error" v-if="error"/>
<form method="post" @submit.prevent="register">
<div class="field">
<label class="label">Username</label>
<div class="control">
<input
type="text"
class="input"
name="username"
v-model="username"
required
>
</div>
</div>
<div class="field">
<label class="label">Email</label>
<div class="control">
<input
type="email"
class="input"
name="email"
v-model="email"
required
>
</div>
</div>
<div class="field">
<label class="label">Password</label>
<div class="control">
<input
type="password"
class="input"
name="password"
v-model="password"
required
>
</div>
</div>
<div class="control">
<button type="submit" class="button is-dark is-fullwidth">Register</button>
</div>
</form>
<div class="has-text-centered" style="margin-top: 20px">
Already got an account? <nuxt-link to="/login">Login</nuxt-link>
</div>
</div>
</div>
</div>
</section>
</template>
<script>
import Notification from '~/components/Notification'
export default {
components: {
Notification,
},
data() {
return {
username: '',
email: '',
password: '',
error: null
}
},
methods: {
async register() {
try {
await this.$axios.post('register', {
username: this.username,
email: this.email,
password: this.password
})
await this.$auth.loginWith('local', {
data: {
email: this.email,
password: this.password
},
})
this.$router.push('/')
} catch (e) {
this.error = e.response.data.message
}
}
}
}
</script>
This contains a form with three fields: username, email and password. Each field is bind to a corresponding data on the component. When the form is submitted, a register
method will be called. Using the Axios module, we make a post request to the /register
endpoint, passing along the user data. If the registration was successful, we make use of the Auth module’s loginWith()
, using the local
strategy and passing the user data to log the user in. Then we redirect the user to the homepage. If there is an error during the registration, we set the error
data as the error message gotten from the API response.
If there is an error, the error message is displayed by a Notification component, which we’ll create shortly.
Before we test the user registration out, let’s create the Notification component. Create a new Notification.vue
file inside components
and paste the code below in it:
// components/Notification.vue
<template>
<div class="notification is-danger">
{{ message }}
</div>
</template>
<script>
export default {
name: 'Notification',
props: ['message']
}
</script>
The Notification component accepts a message
props, which is the error message.
Now, we can test out user registration:
Upon successful registration, we should be logged in but there is no way for us to know whether we are logged in or not for now. So let’s fix that by updating the Navbar component and adding some computed properties.
Before we do just that, let’s first activate the Vuex store by creating an index.js
file inside the store
directory. The Auth module stores user authentication status as well as user details inside Vuex state in an auth
object. So we can check if a user is logged in or not with this.$store.state.auth.loggedIn
, which will either return true
or false
. Similarly, we can get a user details with this.$store.state.auth.user
, which will be null
if no user is logged in.
Note: Though I’ll be using an API built with AdonisJs for the purpose of this tutorial, you are free to use whatever framework that work best for you.
Since we might want to use the computed properties in multiple places in our app, let’s create store getters. Paste the code below intostore/index.js
:
// store/index.js
export const getters = {
isAuthenticated(state) {
return state.auth.loggedIn
},
loggedInUser(state) {
return state.auth.user
}
}
Here, we create two getters. The first one (isAuthenticated
) will return the authentication status of a user and the second (loggedInUser
) will return the details or the logged in user.
Next, let’s update the Navbar component to make use of the getters. Replace the content of components/Navbar.vue
with the following:
// components/Navbar.vue
<template>
<nav class="navbar is-light">
<div class="container">
<div class="navbar-brand">
<nuxt-link class="navbar-item" to="/">Nuxt Auth</nuxt-link>
<button class="button navbar-burger">
<span></span>
<span></span>
<span></span>
</button>
</div>
<div class="navbar-menu">
<div class="navbar-end">
<div class="navbar-item has-dropdown is-hoverable" v-if="isAuthenticated">
<a class="navbar-link">
{{ loggedInUser.username }}
</a>
<div class="navbar-dropdown">
<nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
<hr class="navbar-divider">
<a class="navbar-item">Logout</a>
</div>
</div>
<template v-else>
<nuxt-link class="navbar-item" to="/register">Register</nuxt-link>
<nuxt-link class="navbar-item" to="/login">Log In</nuxt-link>
</template>
</div>
</div>
</div>
</nav>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['isAuthenticated', 'loggedInUser'])
}
}
</script>
We create the computed properties by using the spread operator (…
) to extract the getters from mapGetters
. Then using isAuthenticated
, we display the user menu or links to login or register depending on whether the user is logged in or not. Also, we use loggedInUser
to display the authenticated user username.
Now, if we give our app a refresh, we should see something similar to below:
Now let’s allow returning users ability to login. Create a new login.vue
file inside the pages
directory and paste the code below in it:
// pages/login.vue
<template>
<section class="section">
<div class="container">
<div class="columns">
<div class="column is-4 is-offset-4">
<h2 class="title has-text-centered">Welcome back!</h2>
<Notification :message="error" v-if="error"/>
<form method="post" @submit.prevent="login">
<div class="field">
<label class="label">Email</label>
<div class="control">
<input
type="email"
class="input"
name="email"
v-model="email"
>
</div>
</div>
<div class="field">
<label class="label">Password</label>
<div class="control">
<input
type="password"
class="input"
name="password"
v-model="password"
>
</div>
</div>
<div class="control">
<button type="submit" class="button is-dark is-fullwidth">Log In</button>
</div>
</form>
<div class="has-text-centered" style="margin-top: 20px">
<p>
Don't have an account? <nuxt-link to="/register">Register</nuxt-link>
</p>
</div>
</div>
</div>
</div>
</section>
</template>
<script>
import Notification from '~/components/Notification'
export default {
components: {
Notification,
},
data() {
return {
email: '',
password: '',
error: null
}
},
methods: {
async login() {
try {
await this.$auth.loginWith('local', {
data: {
email: this.email,
password: this.password
}
})
this.$router.push('/')
} catch (e) {
this.error = e.response.data.message
}
}
}
}
</script>
This is quite similar to the register
page. The form contains two fields: email and password. When the form is submitted, a login
method will be called. Using the Auth module loginWith()
and passing along the user data, we log the user in. If the authentication was successful, we redirect the user to the homepage. Otherwise set error
to the error message gotten from the API response. Again, we are using the Notification component from earlier on to display the error message.
Let’s allow logged in user to be able to view their profile. Create a new profile.vue
file inside the pages
directory and paste the code below in it:
// pages/profile.vue
<template>
<section class="section">
<div class="container">
<h2 class="title">My Profile</h2>
<div class="content">
<p>
<strong>Username:</strong>
{{ loggedInUser.username }}
</p>
<p>
<strong>Email:</strong>
{{ loggedInUser.email }}
</p>
</div>
</div>
</section>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['loggedInUser'])
}
}
</script>
This is pretty straightforward, as we are using the loggedInUser
getter from earlier on to display display the user details.
Clicking on the My Profile link should result in something similar to below:
Update the logout link inside the Navbar component as below:
// components/Navbar.vue
<a class="navbar-item" @click="logout">Logout</a>
When the logout link is click, it will trigger a logout
method.
Next, let’s add the logout
method inside the script section of the Navbar component:
// components/Navbar.vue
methods: {
async logout() {
await this.$auth.logout();
},
},
We call the logout()
of the Auth module. This will simply delete the user’s token from localstorage and redirect the user to the homepage.
As it stands now, anybody can visit the profile
page and if the user is not logged in, it will result in error as below:
To fix this, we need to restrict the profile page to only logged in users. Luckily for us, we can easily achieve that with the Auth module. The Auth module comes with an auth
middleware, which we can use in this scenario.
So let’s add the auth
middleware to the profile
page, update the script section as below:
// pages/profile.vue
<script>
...
export default {
middleware: 'auth',
...
}
</script>
Now when a user that is not logged in tries to visit the profile
page, the user will be redirected to the login
page.
Again as it stand, even as a logged in user, we can still access the login and register pages. One way to fix that is to restrict login and register pages to only users that are not logged in. We can do that by creating a guest middleware. Inside the middleware
directory, create a new guest.js
file and paste the code below in it:
// middleware/guest.js
export default function ({ store, redirect }) {
if (store.state.auth.loggedIn) {
return redirect('/')
}
}
A middleware accepts the context as it first argument. So we extract store
and redirect
from the context. We check if the user is logged in then redirect the user to the homepage. Otherwise, we allow the normal execution of the request.
Next, let’s make use of this middleware. Update the script section of both login
and register
as below:
<script>
...
export default {
middleware: 'guest',
...
}
</script>
Now everything should be working as expected.
That’s it! In this tutorial, we looked at how to implement authentication in a Nuxt.js application using the Auth module. We also saw how to keep the authentication flow sleek by making use of middleware.
To learn more and the Auth module, checkout the docs.
Thanks for reading ❤
#vue-js #nuxt-js #javascript #web-development