Guides
Guide

How to Implement Passwordless Auth in Nuxt.js with Magic

How to Implement Passwordless Auth in Nuxt.js with Magic

28 April 2021
Download this example and get started in seconds:
npx make-magic --template nuxt
Banner for How to Implement Passwordless Auth in Nuxt.js with Magic

Hi đź‘‹, in this guide you'll be learning how to add passwordless login to a Nuxt.js application using Magic Link.


Boostrap 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.

npx 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.

Prompt Project Name

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.

Prompt Publishable API Key

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).

Magic Dashboard

You'll now be able to see your Test Publishable API Key - copy and paste the key into your CLI prompt.

Fill Publishable API Key

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.

Prompt NPM Client

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 to an existing one. The authentication process is secured by Magic.

Sign Up Form

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.

Profile Page with User information

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.

# using yarn yarn create nuxt-app magic-nuxtjs # using npx npx create-nuxt-app magic-nuxtjs # using npm npm init nuxt-app magic-nuxtjs

Select these when prompted, and we are all set.

Magic - Nuxt.js Scaffolding

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.

cd magic-nuxtjs yarn dev

We 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:

<template> <div> <Header /> <Nuxt /> </div> </template> <style> *, *::before, *::after { box-sizing: border-box; margin: 0; } * { font-family: sans-serif !important; outline: none; } .container { max-width: 42rem; margin: 0 auto; padding: 0 10px; } </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:

<template> <header> <nav> <ul v-if="$store.state.authenticated"> <li><NuxtLink to="/">Home</NuxtLink></li> <li><NuxtLink to="/profile">Profile</NuxtLink></li> <li><a class="logout" @click="userLogout">Logout</a></li> </ul> <ul v-else> <li> <a class="logout">Magic Nuxt Demo</a> </li> </ul> </nav> </header> </template> <script> export default { methods: { async userLogout() { this.$store.dispatch('logout') }, }, } </script> <style> nav { max-width: 45rem; margin: 0 auto 50px; padding: 1.25rem 1.25rem; border-bottom: 1px solid #f0f0f0; } ul { display: flex; list-style: none; } li { margin-right: 1.5rem; line-height: 38px; } li:first-child { margin-left: auto; } a { text-decoration: none; color: rgba(103, 81, 255, 1); } a:hover { color: rgba(103, 81, 255, 0.5); } .logout { color: rgba(191, 144, 69, 1); cursor: pointer; } .logout:hover { color: rgba(191, 144, 69, 0.8); } </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:

<template> <div class="container"> <div class="login"> <form @submit.prevent="userLogin"> <h3 class="form-header">Login</h3> <div class="input-wrapper"> <input v-model="email" class="input-email" type="email" placeholder="Enter your email" required /> </div> <div class="btn-wrapper"> <button type="submit" class="btn-submit">Send Magic Link</button> </div> </form> </div> </div> </template> <script> export default { data() { return { email: '', } }, mounted() { // If the user is authenticated, redirect to profile page. if (this.$store.state.authenticated) { this.$router.push('/profile') } }, methods: { async userLogin() { this.$store .dispatch('login', { email: this.email, }) .then(() => { this.$router.push('/profile') }) .catch((err) => { console.log(err) }) }, }, } </script> <style> .login { max-width: 20rem; margin: 40px auto 0; padding: 1rem; border: 1px solid #dfe1e5; border-radius: 4px; text-align: center; box-shadow: 0px 0px 6px 6px #f7f7f7; box-sizing: border-box; } form, label { display: flex; flex-flow: column; text-align: center; } .form-header { font-size: 22px; margin: 25px 0; font-weight: 500; } .input-wrapper { width: 80%; margin: 0 auto 20px; } .input-email { padding: 7px 16px; border-radius: 10px; border: 1px solid #000; font-size: 16px; font-weight: 400; } .input-email:focus, .input-email:hover { border: 1px solid #6851ff; } .btn-wrapper { margin: 0px auto 25px; } .btn-submit { padding: 6px 24px; border: 1px solid transparent; border-radius: 2147483647px; background-color: rgba(103, 81, 255, 1); color: #fff; cursor: pointer; outline: none; font-size: 16px; font-weight: 400; } </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:

<template> <div class="container"> <div class="label">Email</div> <div class="profile-info">{{ $store.state.user.email }}</div> <div class="label">Issuer</div> <div class="profile-info">{{ $store.state.user.issuer }}</div> <div class="label">Public Address</div> <div class="profile-info">{{ $store.state.user.publicAddress }}</div> </div> </template> <script> export default { middleware: 'magicauth', } </script> <style> .label { font-size: 12px; color: #6851ff; margin: 30px 0 5px; } .profile-info { font-size: 17px; word-wrap: break-word; } </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 an unauthorized user.

We will learn more about the magicauth middleware later in this guide.

Update Home page

Update our home page and protect it from an unauthorized user.

Open index.vue file under the pages directory and replace it with the following code:

<template> <div class="container"> <div>You are logged in!</div> </div> </template> <script> export default { middleware: 'magicauth', } </script>

Add Magic Authentication

Magic is a plug-and-play SDK that enables passwordless authentication using magic links.

Install Magic

yarn 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.

NUXT_ENV_MAGIC_PUBLISHABLE_KEY=pk_test_

Sign Up with Magic and get your NUXT_ENV_MAGIC_PUBLISHABLE_KEY.

Dashboard Image

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:

import { Magic } from 'magic-sdk'; // Create client-side Magic instance const createMagic = key => { return typeof window != 'undefined' && new Magic(key); }; export 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:

import { magic } from '../plugins/magic'; export const state = () => ({ user: null, authenticated: false, }); export const mutations = { SET_USER_DATA(state, userData) { state.user = userData; state.authenticated = true; }, CLEAR_USER_DATA(state) { state.user = null; state.authenticated = false; this.$router.push('/login'); }, }; export const actions = { async login({ commit }, email) { await magic.auth.loginWithMagicLink(email); const metadata = await magic.user.getMetadata(); commit('SET_USER_DATA', metadata); }, async logout({ commit }) { await magic.user.logout(); commit('CLEAR_USER_DATA'); }, };

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’s loginWithMagicLink function to authenticate the user with their email address. After that, we get the user’s metadata by calling magic.user.getMetadata() function. And commit SET_USER_DATA mutation with metadata 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 the CLEAR_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:

export default function ({ store, redirect }) { // If the user is not authenticated, redirect to login page. if (!store.state.authenticated) { return redirect('/login'); } }

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

yarn 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:

/* * Naming your plugin 'xxx.client.js' will make it execute only on the client-side. * https://nuxtjs.org/guide/plugins/#name-conventional-plugin */ import createPersistedState from 'vuex-persistedstate'; export default ({ store }) => { createPersistedState()(store); };

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:

// Modules: https://go.nuxtjs.dev/config-modules modules: ['@nuxtjs/axios'], plugins: [{ src: '~/plugins/persistedState.client.js' }], /* . . */

Test your persistent state

Let’s start the server:

yarn dev

Your application should be running on http://localhost:3000 and would look like this:

Sign Up Form

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:

Profile Page with User information

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

Deploy with Vercel

Deploy to Netlify

Deploy with Netlify

Let's make some magic!