How to Implement Passwordless Auth in Nuxt.js with Magic
npx make-magic --template nuxt
Hi 👋, in this guide you'll be learning how to add passwordless login to a Nuxt.js application using Magic Link.
#Bootstrap Project
#CLI Quickstart Tool
To start, run the following CLI command in your terminal. The make-magic
NPM package is the quickest way to bootstrap a Magic project from a list of pre-built templates - similar to create-nuxt-app
.
01npx make-magic --template nuxt
#Setup Project Name
After a few seconds, you will be prompted for a project name, this will also be the name of the folder that will be created for this project.
#Magic Publishable API Key
After putting in a project name, you will be prompted for your Magic Publishable API Key, which enables user authentication with Magic.
To get your publishable API key, you'll need to sign up to Magic Dashboard. Once you've signed up, an app will be created upon your first login (you'll be able to create new apps later).
You'll now be able to see your Publishable API Key - copy and paste the key into your CLI prompt
#Select NPM Client
After hitting Enter, you'll be asked to select whether you’d like to use npm / yarn as the NPM client for your project.
#Open Application
After selecting your NPM client, the nuxt server will automatically start, and your application will be running on http://localhost:3000.
In this example app, you'll be prompted to sign up for a new account using an email address or login into an existing one. The authentication process is secured by Magic.
After clicking on your magic link email, you'll be successfully logged in, and redirected to the profile page that displays your email, issuer, and public address.
#Live Demo
https://magic-nuxtjs.vercel.app
Now that you have your Nuxt application running locally, let's dive deep into creating a Nuxt application from scratch and add Magic authentication to it.
#Create a Nuxt application
#Prerequisites
#What is Nuxt?
Nuxt.js is a higher-level framework based on Vue.js to create production-ready modern web applications. Nuxt is inspired by Next.js, which is a framework of similar purpose, based on React.js.
#create-nuxt-app
Let’s start by creating our application using create-nuxt-app
.
01
02# using yarn
03yarn create nuxt-app magic-nuxtjs
04# using npx
05npx create-nuxt-app magic-nuxtjs
06# using npm
07npm init nuxt-app magic-nuxtjs
Select these when prompted, and we are all set.
These are some basic things to get you started with the Nuxt application.
Let’s move into our magic-nuxtjs
directory and start our application.
01cd magic-nuxtjs
02yarn dev
You should have our Nuxt application running on http://localhost:3000.
#Build UI of the application
Let’s start by understanding what we will be building and how our UI would look.
Layout:
- A home page to display the login state.
- A login page to capture the user’s email address.
- A profile page to display the user’s data.
- A header component to include a header on all of our pages.
- And an update to our default layout.
Firstly, we will replace the default.vue
layout.
#Update default layout
Open default.vue
under the layouts
directory and replace it with the following code:
01
02<template>
03 <div>
04 <Header />
05 <Nuxt />
06 </div>
07</template>
08
09<style>
10*,
11*::before,
12*::after {
13 box-sizing: border-box;
14 margin: 0;
15}
16* {
17 font-family: sans-serif !important;
18 outline: none;
19}
20.container {
21 max-width: 42rem;
22 margin: 0 auto;
23 padding: 0 10px;
24}
25</style>
This code adds the <Header />
component to our application, visible to all our pages. Also, it adds basic styling to our application.
With Nuxt.js you can create your components and auto import them into your .vue
files meaning there is no need to manually import them in the script section. Nuxt.js will scan and auto import these for you once you have components set to true
in your nuxt.config.js
file.
#Create Header component
Now, let’s create that <Header />
component.
Create Header.vue
file inside the components
directory and fill it with the following code:
01
02<template>
03
04 <header>
05 <nav>
06 <ul v-if="$store.state.authenticated">
07 <li><NuxtLink to="/">Home</NuxtLink></li>
08 <li><NuxtLink to="/profile">Profile</NuxtLink></li>
09 <li><a class="logout" @click="userLogout">Logout</a></li>
10 </ul>
11 <ul v-else>
12 <li>
13 <a class="logout">Magic Nuxt Demo</a>
14 </li>
15 </ul>
16 </nav>
17 </header>
18</template>
19
20<script>
21export default {
22 methods: {
23 async userLogout() {
24 this.$store.dispatch('logout')
25 },
26 },
27}
28</script>
29
30<style>
31nav {
32 max-width: 45rem;
33 margin: 0 auto 50px;
34 padding: 1.25rem 1.25rem;
35 border-bottom: 1px solid #f0f0f0;
36}
37ul {
38 display: flex;
39 list-style: none;
40}
41li {
42 margin-right: 1.5rem;
43 line-height: 38px;
44}
45li:first-child {
46 margin-left: auto;
47}
48a {
49 text-decoration: none;
50 color: rgba(103, 81, 255, 1);
51}
52a:hover {
53 color: rgba(103, 81, 255, 0.5);
54}
55.logout {
56 color: rgba(191, 144, 69, 1);
57 cursor: pointer;
58}
59.logout:hover {
60 color: rgba(191, 144, 69, 0.8);
61}
62</style>
In this file, we have three navigation links for our header: Home
, Profile
, and Logout
. Home and Profile uses <NuxtLink>
to navigate in between the pages. Logout calls a userLogout()
function, which uses $store
to dispatch a logout
action. This logs the user out and then, if successful, redirects them to the login page.
We will learn more about $store
later in this guide. But for now, let’s continue building our UI.
#Create Login page
Now, we will create a login page where the user can log in with their email address.
Create a login.vue
file inside the pages
directory, and fill it with the following code:
01
02<template>
03 <div class="container">
04 <div class="login">
05 <form @submit.prevent="userLogin">
06 <h3 class="form-header">Login</h3>
07 <div class="input-wrapper">
08 <input
09 v-model="email"
10 class="input-email"
11 type="email"
12 placeholder="Enter your email"
13 required
14 />
15 </div>
16 <div class="btn-wrapper">
17 <button type="submit" class="btn-submit">Send Magic Link</button>
18 </div>
19 </form>
20 </div>
21 </div>
22</template>
23
24<script>
25export default {
26 data() {
27 return {
28 email: '',
29 }
30 },
31 mounted() {
32 // If the user is authenticated, redirect to profile page.
33 if (this.$store.state.authenticated) {
34 this.$router.push('/profile')
35 }
36 },
37 methods: {
38 async userLogin() {
39 this.$store
40 .dispatch('login', {
41 email: this.email,
42 })
43 .then(() => {
44 this.$router.push('/profile')
45 })
46 .catch((err) => {
47 console.log(err)
48 })
49 },
50 },
51}
52</script>
53
54<style>
55.login {
56 max-width: 20rem;
57 margin: 40px auto 0;
58 padding: 1rem;
59 border: 1px solid #dfe1e5;
60 border-radius: 4px;
61 text-align: center;
62 box-shadow: 0px 0px 6px 6px #f7f7f7;
63 box-sizing: border-box;
64}
65form,
66label {
67 display: flex;
68 flex-flow: column;
69 text-align: center;
70}
71.form-header {
72 font-size: 22px;
73 margin: 25px 0;
74 font-weight: 500;
75}
76.input-wrapper {
77 width: 80%;
78 margin: 0 auto 20px;
79}
80.input-email {
81 padding: 7px 16px;
82 border-radius: 10px;
83 border: 1px solid #000;
84 font-size: 16px;
85 font-weight: 400;
86}
87.input-email:focus,
88.input-email:hover {
89 border: 1px solid #6851ff;
90}
91.btn-wrapper {
92 margin: 0px auto 25px;
93}
94.btn-submit {
95 padding: 6px 24px;
96 border: 1px solid transparent;
97 border-radius: 2147483647px;
98 background-color: rgba(103, 81, 255, 1);
99 color: #fff;
100 cursor: pointer;
101 outline: none;
102 font-size: 16px;
103 font-weight: 400;
104}
105</style>
In this file, we have a form with an email field and a Send Magic Link
button. When the button is clicked, it triggers the userLogin
function, which uses $store
to dispatch a login
action with the user’s email as a data object.
We will learn more about $store
later in the guide. But for now, assume the login has happened successfully, and that the page is redirected to the /profile
route.
#Create Profile page
Let’s create our profile page to display the user’s data after a successful login.
Create a profile.vue
file inside the pages
directory, and fill it with the following code:
01
02<template>
03 <div class="container">
04 <div class="label">Email</div>
05 <div class="profile-info">{{ $store.state.user.email }}</div>
06
07 <div class="label">Issuer</div>
08 <div class="profile-info">{{ $store.state.user.issuer }}</div>
09
10 <div class="label">Public Address</div>
11 <div class="profile-info">{{ $store.state.user.publicAddress }}</div>
12 </div>
13</template>
14
15<script>
16export default {
17 middleware: 'magicauth',
18}
19</script>
20
21<style>
22.label {
23 font-size: 12px;
24 color: #6851ff;
25 margin: 30px 0 5px;
26}
27.profile-info {
28 font-size: 17px;
29 word-wrap: break-word;
30}
31</style>
This page displays the Email
, Issuer
, and Public Address
of an authenticated user. Here, the Issuer
is the user’s unique ID given by Magic. It uses magicauth
middleware to protect this page from unauthorized users.
We will learn more about the magicauth
middleware later in this guide.
#Update Home page
Update our home page and protect it from unauthorized users.
Open index.vue
file under the pages
directory and replace it with the following code:
01
02<template>
03
04 <div class="container">
05 <div>You are logged in!</div>
06 </div>
07</template>
08
09<script>
10export default {
11 middleware: 'magicauth',
12}
13</script>
#Add Magic Authentication
Magic is a plug-and-play SDK that enables passwordless authentication using magic links.
#Install Magic
01yarn add magic-sdk
#Create .env file
Create a .env
file at the root of your application and add NUXT_ENV_MAGIC_PUBLISHABLE_KEY
to it.
01NUXT_ENV_MAGIC_PUBLISHABLE_KEY=pk_live_abc
Sign Up with Magic and get your NUXT_ENV_MAGIC_PUBLISHABLE_KEY
.
Note: When moving to Production, we recommend that you whitelist your domain for your Publishable API Key
on the Magic Dashboard so that your API keys only work on your domain.
#Magic Nuxt Plugin
Create a magic.js
file under the plugins
directory and fill it with the following code:
01
02import { Magic } from 'magic-sdk';
03
04// Create client-side Magic instance
05const createMagic = key => {
06 return typeof window != 'undefined' && new Magic(key);
07};
08
09export const magic = createMagic(process.env.NUXT_ENV_MAGIC_PUBLISHABLE_KEY);
Here we instantiate the Magic Client SDK, which we will use in our store.
#Build Store and Middleware
#Store
Create a index.js
under the store
directory and fill it with the following code:
01
02import { magic } from '../plugins/magic';
03
04export const state = () => ({
05 user: null,
06 authenticated: false,
07});
08
09export const mutations = {
10 SET_USER_DATA(state, userData) {
11 state.user = userData;
12 state.authenticated = true;
13 },
14 CLEAR_USER_DATA(state) {
15 state.user = null;
16 state.authenticated = false;
17 this.$router.push('/login');
18 },
19};
20
21export const actions = {
22 async login({ commit }, email) {
23 await magic.auth.loginWithMagicLink(email);
24 const metadata = await magic.user.getMetadata();
25 commit('SET_USER_DATA', metadata);
26 },
27 async logout({ commit }) {
28 await magic.user.logout();
29 commit('CLEAR_USER_DATA');
30 },
31};
The store
directory contains the Vuex Store files. The Vuex Store comes with Nuxt.js out of the box but is disabled by default. Creating an index.js
file in this directory enables the store.
We include the magic.js
plugin at the top of the file. Using Vuex with Nuxt, we export the state as a function and the mutations and actions as objects.
We have two states, user
and authenticated
. The user
stores the user’s metadata received by Magic. And, authenticated
stores the state of the authenticated user.
Then, we have two mutations
and two actions
.
- The
login
action calls Magic’sloginWithMagicLink
function to authenticate the user with their email address. After that, we get the user’s metadata by callingmagic.user.getMetadata()
function. And commitSET_USER_DATA
mutation withmetadata
as payload, which changes the user and authenticated state. - The
logout
action calls Magic’s logout function to log out a user from the Magic end. And commit theCLEAR_USER_DATA
mutation, which removes the user object and turns authenticated to false. Then redirects the user to the login page.
#Middleware
Create a magicauth.js
the under middleware
directory and fill it with the following code:
01
02export default function ({ store, redirect }) {
03 // If the user is not authenticated, redirect to login page.
04 if (!store.state.authenticated) {
05 return redirect('/login');
06 }
07}
Middleware lets you define custom functions that can be run before rendering either a page or a group of pages (layout).
We have defined a magicauth
middleware, which checks the user’s authenticated state and redirects the user accordingly.
Our application is ready now. You can start the server by running yarn dev
or npm run dev
. Everything will work fine, but there’s a catch. When you try to reload the page, the Vuex state loses its persistent state. So, let’s fix that by persisting the State.
#Persist State
To persist the state, we are going to use the vuex-persistedstate
package.
#Install vuex-persistedstate
01yarn add vuex-persistedstate @nuxtjs-axios
Note: vuex-persistedstate
uses @nuxtjs/axios
as its dependency. It will not work if not installed and configured properly.
#persistedState
Create a persistedState.client.js
file under the plugin directory and fill it with the following code:
01
02/*
03 * Naming your plugin 'xxx.client.js' will make it execute only on the client-side.
04 * https://nuxtjs.org/guide/plugins/#name-conventional-plugin
05 */
06import createPersistedState from 'vuex-persistedstate';
07
08export default ({ store }) => {
09 createPersistedState()(store);
10};
And we want to persist the state at the client’s side. Nuxt is both for a client and a server-side application. To learn more about what Nuxt can do, visit https://nuxtjs.org.
#Update nuxt.config.js
Open nuxt.config.js
file, add @nuxtjs/axios
inside the modules
and persisted plugin inside the plugins
:
01
02// Modules: https://go.nuxtjs.dev/config-modules
03modules: ['@nuxtjs/axios'],
04
05plugins: [{ src: '~/plugins/persistedState.client.js' }],
06/*
07.
08.
09*/
#Test your persistent state
Let’s start the server:
01
02yarn dev
Your application should be running on http://localhost:3000 and would look like this:
Enter your email address, and click Send Magic Link
. An email will come to your email address. Open that mail, click on the button that says Log in to {your-application}
. Come back to the open window. You will be redirected to the profile page, which would look like this but with your details:
Try reloading the page. Your data is persisted now.
If you run into any issues, please share those in the comments below.
#Done
Congratulations! You have successfully developed a Nuxt.js application that has user authentication built with Magic.
#Deploy
#Deploy to Vercel
Click here to deploy with Vercel.
#Deploy to Netlify
Click here to deploy with Vercel.