Adding magic links to your Next.js application using FusionAuth

In this guide, you will build a simple web application using Next.js that allows a user to sign up and then login using a magic link sent to their email address.

Authors

Published: March 6, 2023


In this guide, you’ll build a web application using Next.js that allows a user to sign up and then login using a magic link sent to their email address.

The completed application will include a public page that allows users to either sign up or login via FusionAuth, as well as a private page where they will be able to view profile information once they’ve logged in using OpenID Connect. Additionally, users will be able to log out and view the public page.

If you’d prefer to skip ahead and take a look at the code, view it here.

Prerequisites

This tutorial will include all the necessary information for you to understand magic links, but it would be beneficial if you had:

  • Basic Typescript knowledge (or JavaScript ES6)
  • An understanding of Next.js (or some knowledge of a similar full-stack web framework)
  • Experience running applications using Docker and Docker Compose

Magic links are a type of passwordless authentication in which FusionAuth sends a secure, one time, timebound encoded code to a user. This code is usually delivered via email but it can also be sent through any other transport mechanism.

Using magic links can ease a user’s sign-in experience because the ownership of the code is proof of who they are. No other information is needed and the user can click the link and be logged in, as if by magic.

An additional security benefit to passwordless like this login is that users are unable to use the same password across different websites or applications, implicitly protecting them, and you, against illicit access to your system in the case of data breaches.

Installing and configuring FusionAuth using Docker Compose

There are a number of ways you can get up and running with FusionAuth on your system. If you have Docker installed on your system, the best choice is to use Docker and Docker Compose.

You can follow the guide here to get started.

If you already have Docker installed on your system, run the following command to install and run FusionAuth.

curl -o docker-compose.yml https://raw.githubusercontent.com/FusionAuth/fusionauth-containers/master/docker/fusionauth/docker-compose.yml
curl -o .env https://raw.githubusercontent.com/FusionAuth/fusionauth-containers/master/docker/fusionauth/.env
docker compose up

This configuration uses a publicly available .env file containing hard-coded database passwords to configure FusionAuth. This is not recommended for production use.

Configuring FusionAuth

After running the above commands, FusionAuth should be running and accessible on your system via http://localhost:9011.

The first time you visit this URL, you will be required to set up an administrator account and password.

Once you’ve set up your admin account, you’ll be presented with three more steps to complete setting up FusionAuth, shown in the image below.

FusionAuth prompts you with the setup steps that you need to complete

For this tutorial, setting up an application and setting up an SMTP server is important. You’ll walk through how to do this below. You don’t need to set up an API key.

Creating an application

The first thing you’ll need to do is set up an application. Click “Setup” under “Missing Application”.

This will take you to the “Add Application” page where you can create your application. Call the application “Next.js Example” or pick another name that you would like to use.

Take note of the Client Id and Client Secret that FusionAuth provides you with for this application. You’ll use them later in the code.

Once you’re done, click the save icon and your application will be added.

Now you need to configure OAuth for your newly created application. The fields you will need to configure are:

  • Authorized redirect URLs, set to http://localhost:3000/api/auth/callback/fusionauth.
  • Authorized request origin URL, set to http://localhost:3000.
  • Logout URL, set to http://localhost:3000/api/auth/logout.

These URLs correspond to the hostname and paths you will use for our Next.js application.

Make sure that you change PKCE (Proof Key for Code Exchange) to “Required”.

Lastly, before hitting “Save” on this page, ensure that “Authorization Code” and “Refresh Token” are checked in the “Enabled grants” section.

After you’ve completed the above steps, your application configuration should look similar to the below image.

Completed configuration for application URLs in FusionAuth

Enabling self-service registration

You will want to enable your users to register for accounts using the FusionAuth login page. This will enable FusionAuth to create and register users in one step.

To enable self-service registrations, select the “Registration” tab from the “Edit Application” page. Scroll down to “Self-service registration” and set the toggle to “Enabled”. Choose “Basic” as the registration type and set the login type to “Email”. You can select “Username” to allow users to login using their usernames, but for the purposes of this tutorial, use email.

You can also optionally select any fields that you would like to capture during registration. For this tutorial, no other registration fields are required.

Once you’ve done the above, your setup should look similar to this:

Completed configuration for registration options in FusionAuth

Click the save icon in the top right for your changes to be applied to your application.

Setting up OpenID Connect (OIDC)

Once a user has logged in to our application using FusionAuth, you want to be able to retrieve details about their profile. You can achieve this using OIDC, implemented by FusionAuth. To configure OIDC to work correctly, you need to configure FusionAuth’s JWT issuing behavior.

Firstly, head to your tenant configuration via the FusionAuth sidebar and edit the default tenant. Set the issuer field to your fully qualified URL (including the protocol). This will be something like http://localhost:9011. The issuer signs and issues tokens when users login.

Completed configuration for an issuer URL for a tenant in FusionAuth

Now hit save and navigate to the application configuration for our application and edit it. On the edit page, select the “JWT” tab and enable JWT by setting the toggle to “Enabled”.

Change the configuration under “JSON Web Token settings” to have the following values:

  • “JWT duration”: 3600.
  • ”Access Token signing key”: Select “Auto generate a new key on save”.
  • ”Id Token signing key”: Select “Auto generate a new key on save”.

Click the save icon in the top right to save your changes. Your resulting application configuration should be similar to the following:

Completed JWT configuration for an application in FusionAuth

Setting up an SMTP server for email

For this tutorial, you’re going to use SendGrid as an SMTP provider. This will allow FusionAuth to send emails to your users as needed via SendGrid.

To get started with SendGrid and SMTP, follow their guide here. After you’ve set up an API key, be sure to set up a Sender Identity for the email address you want to send emails from.

Take special note of the API key that SendGrid provides you with, as that can only be accessed once and will be used in your configuration.

To enable your FusionAuth instance to use SMTP, you need to make a few changes to your tenant configuration. A full walkthrough of email configuration can be found here.

Change the following fields under the “Email” tab in your tenant configuration:

  • Set the “Host” to smtp.sendgrid.net
  • Set the “Port” to 587
  • Set the “Username” to apikey (This should be string “apikey”, and not your API key)
  • Select the “Change password” toggle and set the “Password” to the SendGrid API key that you created
  • Change the “Default from address” to match the Sender Identity you configured

Your final configuration should look similar to the following:

Completed SMTP configuration for a tenant in FusionAuth

Once you’ve saved your configuration, you should be able to successfully send a test email by clicking the “Send test email” button. If you receive an email that looks like the below, you’re all set!

A test email that was successfully sent via FusionAuth

Note: If you’re using a free email address (e.g. @gmail.com), your emails might go to spam folders, so make sure to check that.

Before jumping into building your web application, make one final configuration change to enable passwordless login using the FusionAuth dashboard.

Navigate to your application configuration and toggle “Passwordless login” to “enabled” under the “Security” tab.

Once done, your configuration should look similar to the image below:

Configuration for a tenant with passwordless login enabled in FusionAuth

Lastly, to ensure that your users can receive emails from FusionAuth, double-check whether the “From Email” is set to the email you configured for your Sender Identity above.

You can do this by navigating to “Email Templates”, under the “Customizations” menu option and editing the email template labeled “[FusionAuth Default] Passwordless Login”. Change the “From Email” field to your email address. Save your changes once you’re done. Your completed configuration should look similar to the image below.

Complete configuration for an email template in FusionAuth

Using Kickstart

You can also use this sample kickstart file to set up everything if you don’t want to do these steps manually. Make sure to review and update the kickstart file.

Learn more about Kickstart here.

Building the Next.js application

Now that you’ve set up all the required FusionAuth configuration, you can start building your application.

First, ensure you have the following installed on your system:

  • Node.js v18.10.0
  • npm v8.19.2

This example should work with other versions of these requirements, but the above are what were used for this post.

Kicking off the project

To create a new Next.js project, you can use the create-next-app tool. To do this, run the following command in your terminal:

npx create-next-app --typescript

This will take you through a few set-up steps, which result in a functional Next.js application.

You should be able to run your application by running the following command in your terminal once in the directory Next.js generated for you:

npm run dev

You’ll see the following output:

> dev
> next dev

ready - started server on 0.0.0.0:3000, url: http://localhost:3000
event - compiled client and server successfully in 1192 ms (154 modules)

Your application will now be accessible via localhost:3000 and you should see the below screen if you navigate to localhost:3000 in your browser.

A simple Next.js application displayed in a browser

Using NextAuth.js

You will be using NextAuth.js to interface with FusionAuth from our Next.js application. It is the most popular and widely used authentication solution for Next.js applications.

To add NextAuth.js to your application, run the following command in the root of the project:

npm install --save next-auth

Once installed, you can configure a dynamic route handler for NextAuth.js which will contain your FusionAuth configuration.

Create a new directory called auth in pages/api. Inside the newly created directory, create a new file called [...nextauth].ts, containing the following code:

import NextAuth from "next-auth"
import FusionAuthProvider from "next-auth/providers/fusionauth"
export const authOptions = {
  providers: [
    FusionAuthProvider({
      id: "fusionauth",
      name: "FusionAuth",
      issuer:  process.env.FUSIONAUTH_ISSUER,
      clientId: process.env.FUSIONAUTH_CLIENT_ID,
      clientSecret: process.env.FUSIONAUTH_SECRET,
    }),
  ],
}
export default NextAuth(authOptions)

To provide your Next.js application with the required fields for connecting to FusionAuth, create a file called .env.local in the root of your project, as follows:

FUSIONAUTH_ISSUER=YOUR_ISSUER_URL
FUSIONAUTH_CLIENT_ID=YOUR_CLIENT_ID
FUSIONAUTH_SECRET=YOUR_SECRET

By default, Next.js will load up all environment variables from a .env.local file into process.env.

If you need to find your Client Id and Client Secret, navigate to your application configuration in FusionAuth and look under the OAuth section. The issuer URL is the URL to your FusionAuth instance, including the protocol. It might be something like http://localhost:9011.

The final step in setting up NextAuth.js is to use the SessionProvider to wrap your application.

Replace the code in __app.tsx with the following:

import type { AppProps } from 'next/app'
import { SessionProvider } from 'next-auth/react';

function MyApp({ Component, pageProps: {
  session,
  ...pageProps
} }: AppProps) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />
    </SessionProvider>
  )
}

export default MyApp

Creating a basic sign in and sign out flow

Your users are going to need a button to kick off the login flow. To create this button, add a file called login-btn.tsx in pages/components, containing the following:

import { useSession, signIn, signOut } from "next-auth/react"
export default function LoginButton() {
  const { data: session } = useSession();

  if (session && session.user) {
    return (
      <>
        <p>Signed in as {session.user.email}</p> 
        <br />
        <button onClick={() => signOut()}>Sign out</button>
      </>
    )
  }
  return (
    <>
      <p>Not signed in</p>
      <br />
      <button onClick={() => signIn("fusionauth")}>Sign in</button>
    </>
  )
}

The above component uses the useSession() hook provided by NextAuth.js to dynamically render either a “Sign in” or “Sign out” button.

Once you’ve created the login-btn.tsx component, you can add it to your index.tsx file.

Replace the code in index.tsx with the following:

import type { NextPage } from 'next'
import Head from 'next/head'
import LoginButton from './components/login-btn'

const Home: NextPage = () => {
  return (
    <div>
      <Head>
        <title>Welcome to FusionAuth + Next.js</title>
      </Head>
      <h1>Welcome to FusionAuth + Next.js</h1>
      <LoginButton />
    </div>
  )
}

export default Home

Once you’ve done this, your application should have a basic sign in and sign out flow. You can test it by navigating to http://localhost:3000 and clicking “Sign In”.

Next.js application with a signin button

Clicking “Sign in” will take you to the FusionAuth sign-in form, where you’ll be able to see all the available sign-in options. Click “Login with a magic link”.

The FusionAuth Oauth2 login form including a passwordless log in option

After clicking the button, you should be taken to the “Passwordless login” page, where you can enter the email address to which a magic link will be sent.

The FusionAuth passwordless login form

Once you submit your email address, you receive an email from FusionAuth containing your magic link, similar to the image below.

An email from FusionAuth containing a magic link

Note: If you’re using a free email address (e.g. @gmail.com) to send the magic link, the emails might go to your spam folder, so make sure to check it.

Clicking the link from the email will log you in and redirect you back to your application, like below.

Next.js application with a sign out button, after a user has signed in

Adding a protected page

It is a common use case to want to have pages in an application that are only accessible once a user is signed in. You can do this easily with Next.js and FusionAuth.

Create a file name protected.tsx in your pages/ directory, containing the following code:

import { useSession, getSession } from "next-auth/react"
import LoginButton from "./components/login-btn"

export default function Protected() {
  const { data: session, status } = useSession()

  if (status === "loading") {
    return <p>Loading...</p>
  }

  if (status === "unauthenticated") {
    return (
      <>
      <p>Access Denied</p>
      <p>Sign in to view this page</p>
      <hr/>
      <LoginButton />
      </>
    )
  }

  return (
    <>
      <h1>Protected Page</h1>
      <p>You can view this page because you are signed in.</p>
      <hr/>
      <LoginButton />
    </>
  )
}

In a similar way to your login button, this code uses the useSession hook to check whether a user is logged in and dynamically renders content based on that information.

Navigating to http://localhost:3000/protected while not logged in, displays the following screen.

Next.js application displaying a protected page to a user that is not signed in

Once you’ve signed in with FusionAuth, you can see the contents of the page:

Next.js application displaying a protected page to a user that is signed in

Next steps

This simple application demonstrates a very small slice of the capabilities that passwordless login offers but features a fully functional authentication system, without you having to build it. The complete code for this tutorial is available on GitHub, here.

To make this application more useful and production-ready, you could offer multiple channels by which users can receive magic links. You can do this using the passwordless API directly.

Additionally, you could use themes to customize the look and feel of the FusionAuth login form to display the login options you want to offer your users. FusionAuth provides you with a full set of capabilities around authentication to make it safer and easier.

More on nextjs

Subscribe to The FusionAuth Newsletter

A newsletter for developers covering techniques, technical guides, and the latest product innovations coming from FusionAuth.

Just dev stuff. No junk.