In this tutorial, you’ll implement authentication in a Nuxt.js app using the Auth
module.
For the purpose of this tutorial, you’ll be using JWT
for authentication.
Below is a quick demo of what you’ll be building in this tutorial:
You can find the source code for this application at GitHub.
Warning: Several of the packages in this tutorial now contain dependencies with known vulnerabilities. In a production setting, you would resolve these issues by upgrading these packages, finding alternatives, or creating forked versions with patched fixes. However, within the limited context of a tutorial, it provides educational value as-is.
To complete this tutorial, you will need:
Some familiarity with Vue.js and Nuxt.js may be beneficial. You can refer to this post if you’re getting started with Nuxt.js.
This tutorial was verified with Node v13.13.0, npm v6.14.4, vue
v2.6.11, and nuxt
v2.12.2.
You are free to use whatever framework that works best for you. However, for quick development, this tutorial will clone an API built with AdonisJs.
The API utilizes:
The API has three endpoints:
/register
: endpoint for user registration/login
: endpoint for authenticating users/me
: endpoint for getting details for the currently authenticated user and it is protected by an auth
middleware, which means a user must be authenticated to access the endpointFirst, run the following command in your terminal window:
- git clone https://github.com/do-community/jwt-auth-api.git
Then, navigate to the project directory:
- cd jwt-auth-api
And install the API dependencies:
- npm install
Note: When running install, you may encounter issues with sqlite3
version 4.0.1
depending on the version of Node you are running. Refer to the changelog to determine compatibility with your environment.
At the time of original publication, the latest version of Node was 10. One option is to downgrade your version of Node to 10.20.1
(with the understanding that it is nearing end-of-life support). Then, run npm install
.
A second option is to remove the package-lock.json
file which will cause the system to look for 4.2.0
which is supported up to Node 13. You may need to also downgrade your version of Node to 13.13.0
. Then, run npm install
.
A third option would be to modify package.json
to a version of sqlite3
supported by your current version of Node, remove package-lock.json
, and run npm install
. However, at the time of testing, 5.0.0
is not yet released to handle Node 14+ support.
Other symptoms of incompatibility include the following errors: TypeError: Cannot read property 'data' of undefined
and Error: Cannot find module '[...]/node_modules/sqlite3/lib/binding/[...]/node_sqlite3.node'
.
Next, rename .env.example
to .env
:
- mv .env.example .env
And generate an APP_KEY
:
- npx @adonisjs/cli@4.0.12 key:generate
You should see:
- Outputgenerated: unique APP_KEY
Once that’s coomplete, let’s run the migrations:
- npx @adonisjs/cli@4.0.12 migration:run
Now, you can start the API:
- # ensure that you are in the `jwt-auth-api` project directory
- npm start
You can access the API on http://127.0.0.1:3333/api
. Leave this running in a terminal window for the rest of the duration of the tutorial.
Now, you can create a Nuxt.js app. Open a new terminal window and use vue-cli
to initialize a new Vue project with the Nuxt starter template:
- npx vue-cli@2.9.6 init nuxt/starter nuxt-auth
Note: At the time of testing, vue-cli
is deprecated. @vue/cli
is the current command line tool for Vue projects. And @vue/cli-init
is the recommended approach for legacy vue-cli
projects. However, create-nuxt-app
is the recommended approach for modern Nuxt projects.
Next, you need to navigate to the project directory:
- cd nuxt-auth
And install the dependencies:
npm install
Then, you can launch the app:
- npm run dev
The app should be running on http://localhost:3000
. You can view the application in a web browser to see the default Vue application created by vue-cli
.
Now, let’s install the Nuxt.js modules that you’ll be needing for your app. You’ll be using the Nuxt Auth module and the Nuxt Axios module, since the auth
module makes use of Axios internally:
- # ensure that you are in the `nuxt-auth` project directory
- npm install @nuxtjs/auth@4.5.1 @nuxtjs/axios@5.3.1 --save
Once that’s completed, open nuxt.config.js
:
- nano nuxt.config.js
Add the code below to nuxt.config.js
:
module.exports = {
// ...
modules: [
'@nuxtjs/axios',
'@nuxtjs/auth'
],
}
Note: At this point, newer versions of Nuxt may encounter the error: Enable vuex store by creating 'store/index.js'
. This error can be resolved by adding an empty index.js
file to the store
directory.
Next, you need to set up the modules. Paste the code below into nuxt.config.js
:
module.exports = {
// ...
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, you set the base URL that Axios will use when making requests. In our case, we are referencing the sample API we set up earlier.
Then, you define the authentication endpoints for the local
strategy corresponding to those on your API:
token
object inside a data
object./me
endpoint will be inside a data
object.logout
to false
since your API doesn’t have an endpoint for logout. You’ll just remove the token from localStorage when a user logs out.To style your app, you can make use of Bulma.
Open nuxt.config.js
and paste the code below within the link
object that is inside the head
object:
module.exports = {
// ...
head: {
// ...
link [
// ...
{
rel: 'stylesheet',
href: 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css'
}
]
},
// ...
}
Now, let’s create the Navbar component:
- nano components/Navbar.vue
And add the following code:
<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>
<template>
<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>
The Navbar component contains links to login
, register
, profile
, and logout
.
Next, let’s update the default layout to make use of the Navbar
component.
Open default.vue
:
- nano layouts/default.vue
And replace the content with the following:
<template>
<div>
<Navbar/>
<nuxt/>
</div>
</template>
<script>
import Navbar from '~/components/Navbar'
export default {
components: {
Navbar
}
}
</script>
Also, let’s update the homepage.
Open index.vue
:
- nano pages/index.vue
And replace the content with the following:
<template>
<section class="section">
<div class="container">
<h1 class="title">Nuxt Auth</h1>
</div>
</section>
</template>
At this point, you should have an application that displays a title of "Nuxt Auth"
with a header bar with navigation links:
Inside the pages
directory, create a new register.vue
file:
- nano pages/register.vue
And add the following code:
<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 bound to corresponding data on the component. When the form is submitted, a register
method will be called. Using the Axios module, you make a post request to the /register
endpoint, passing along the user data. If the registration was successful, you make use of the Auth module’s loginWith()
, using the local
strategy and passing the user data to log the user in. Then, you redirect the user to the homepage. If there is an error during the registration, you 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.
Create a new Notification.vue
file inside components
:
- nano components/Notifaction.vue
And paste the code below in it:
<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, you can test out user registration:
Upon successful registration, users should be logged in but there is currently no way for the app to know whether users are logged in or not. So let’s fix that by updating the Navbar component and adding some computed properties.
Before you 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 you can check if a user is logged in or not with this.$store.state.auth.loggedIn
, which will either return true
or false
. Similarly, you can get a user’s details with this.$store.state.auth.user
, which will be null
if no user is logged in.
Note: You can also access the user authentication status as well as the user details directly with the Auth module using this.$auth.loggedIn
and this.$auth.user
respectively.
Since you might want to use the computed properties in multiple places in your app, let’s create store getters.
Open index.js
:
- nano store/index.js
And paste the code below in it:
export const getters = {
isAuthenticated(state) {
return state.auth.loggedIn
},
loggedInUser(state) {
return state.auth.user
}
}
Here, you 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:
<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>
You create the computed properties by using the spread operator (...
) to extract the getters from mapGetters
. Then using isAuthenticated
, you display the user menu or links to login
or register
depending on whether the user is logged in or not. Also, you use loggedInUser
to display the authenticated user username.
Now, if you give your app a refresh, you should see something similar to below:
Now, let’s allow returning users the ability to log in.
Create a new login.vue
file inside the pages
directory:
nano pages/login.vue
And paste the code below in it:
<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, you log the user in. If the authentication was successful, you redirect the user to the homepage. Otherwise, set error
to the error message gotten from the API response. Again, you are using the Notification component from earlier on to display the error message.
Let’s allow logged in users to view their profile.
Create a new profile.vue
file inside the pages
directory:
- nano pages/profile.vue
And paste the code below in it:
<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>
Notice how you are using the loggedInUser
getter from earlier on to display the user details.
Clicking on the My Profile link should result in a My Profile page being displayed.
Update the logout link inside the Navbar component.
Open Navbar.vue
:
- nano components/Navbar.vue
Modify the logout link to use @click="logout"
:
// ...
<div class="navbar-dropdown">
<nuxt-link class="navbar-item" to="/profile">My Profile</nuxt-link>
<hr class="navbar-divider"/>
<a class="navbar-item" @click="logout">Logout</a>
</div>
// ...
When the logout link is clicked, it will trigger a logout
method.
Next, let’s add the logout
method inside the script section of the Navbar component:
// ...
export default {
// ...
methods: {
async logout() {
await this.$auth.logout();
},
},
}
You call the logout()
of the Auth module. This will 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 an error.
To fix this, you need to restrict the profile page to only logged in users. Luckily for us, you can achieve that with the Auth module. The Auth module comes with an auth
middleware, which you can use in this scenario.
So let’s add the auth
middleware to the profile
page. Update the script
section as below:
// ...
export default {
middleware: 'auth',
// ...
}
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 stands, even as a logged in user, you 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. You can do that by creating a guest middleware.
Inside the middleware
directory, create a new guest.js
file:
- nano middleware/guest.js
And paste the code below in it:
export default function ({ store, redirect }) {
if (store.state.auth.loggedIn) {
return redirect('/')
}
}
A middleware accepts the context as its first argument. So you extract store
and redirect
from the context. Then, you check if the user is logged in then redirect the user to the homepage. Otherwise, you 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:
// ...
export default {
middleware: 'guest',
// ...
}
Now, everything will be working as expected.
In this tutorial, you looked at how to implement authentication in a Nuxt.js application using the Auth module. You also saw how to keep the authentication flow sleek by making use of middleware.
To learn more about the Auth module, check out the docs.
If you’d like to learn more about Vue.js, check out our Vue.js topic page for exercises and programming projects.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
there is not check token ?
Unknown middleware guest An error occurred while rendering the page. Check developer tools console for details.
Where does Nuxt store the token? Will it be vulnerable to XSS or CSRF?
Awesome blog, really helpful. just small typo in file name Notifaction
Thank you for this great tutorial.I have a issue.
methods: { async logout() { await this.$auth.logout(); }, },
this method not redirecting to the homepage.
Requesting a async call from http://127.0.0.1:000 to http://127.0.0.1:3333/api endpoint will generate CORS issue