Testing Next Auth email magic links with Playwright and MailSlurp
Next Auth email login tutorial and end-to-end testing guide.
Next Auth is a popular package for adding authentication and login to NextJS apps. It supports a range of authentication providers including email magic links. This post shows you how to test Next Auth email magic links using Playwright and MailSlurp.
All code for this tutorial can be found on Github
Setup Next
To send email links we'll need to setup a Next project and add Next Auth using the email provider. First let's set up a Next project
npx create-next-app@latest
Next add the dependencies we need for Next Auth and Playwright:
npm install --save next next-auth nodemailer mailslurp-client
Email provider requires a database, let's use sequelize:
npm install --save sequelize sqlite3 @auth/sequelize-adapter
Configure the email provider
Inside pages/api/auth/[...nextauth].ts
add the following:
import NextAuth, { NextAuthOptions } from "next-auth"
import EmailProvider from "next-auth/providers/email"
import SequelizeAdapter from "@auth/sequelize-adapter";
import {Sequelize} from "sequelize";
const sequelize = new Sequelize("sqlite::memory:");
const adapter= SequelizeAdapter(sequelize);
// create tables
sequelize.sync();
export const authOptions: NextAuthOptions = {
providers: [
EmailProvider({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD
}
},
from: process.env.EMAIL_FROM,
}),
],
adapter: adapter as any,
callbacks: {
async jwt({ token }) {
token.userRole = "admin"
return token
},
},
}
export default NextAuth(authOptions)
Configure the email server
To allow Next to send emails on our behalf we need an SMTP server such as MailSlurp.
- Create a free account and obtain an API key.
- Get your SMTP access details from the homepage.
- Create an inbox with SMTP enabled.
Next create a .env.local
file in the root of your project and add the following:
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=add-random-secret-here
EMAIL_SERVER_HOST=mailslurp.mx
EMAIL_SERVER_PORT=2587
EMAIL_SERVER_USER=add-smtp-username-here
EMAIL_SERVER_PASSWORD=add-smtp-password-here
EMAIL_FROM=add-email-sending-address-here
Running the app
When we run npm run dev
we are greeted by the standard next auth app:
If we click on a restricted page we can see the access denied screen:
Let's write a test now to test the login functionality using MailSlurp to capture the magic link and Playwright to submit it to the app.
Setup testing
First install Playwright:
npm install --save-dev playwright
Then configure the playwright config to start our server before tests:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
reporter: 'html',
use: {
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
/* Run your local dev server before starting the tests */
webServer: {
command: 'npm run start',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},
});
Writing a test for email links
Now we can write a test that performs the following actions:
- Create a new MailSlurp inbox
- Load the app and try login
- Wait for the email to arrive
- Extract the magic link from the email
- Visit the magic link in the browser and see the restricted page
Here is the test:
import { test, expect } from '@playwright/test';
import { MailSlurp } from 'mailslurp-client';
import { writeFile } from 'fs/promises'
const apiKey = process.env.API_KEY ?? '';
test('can login using magic link', async ({ page }) => {
expect(apiKey, "MailSlurp API_KEY env should be set").toBeTruthy()
// create an inbox for sign up
const mailslurp = new MailSlurp({ apiKey })
const userInbox = await mailslurp.createInbox()
await page.goto('http://localhost:3000/');
// load the app and try access
await page.goto('http://localhost:3000/protected/');
await page.waitForSelector('[data-id="access-denied"]')
// now login
await page.click('[data-id="access-link"]')
await page.waitForURL(/signin/);
// try sign in with email
await page.fill('#input-email-for-email-provider', userInbox.emailAddress)
await page.click('#submitButton')
await page.waitForURL(/verify-request/)
// wait for the login link email to arrive
const email = await mailslurp.waitForLatestEmail(userInbox.id)
// use mailslurp to extract the link
const { links: [loginLink] } = await mailslurp.emailController.getEmailLinks({
emailId: email.id,
})
expect(loginLink).toContain('localhost');
// now go to link
await page.goto(loginLink)
await page.waitForSelector('[data-id="access-permitted"]')
});
Running the test
When we run npx playwright test
we see the browser load the sign in page and fill it with a MailSlurp email address:
Once submitted the page shows the verify request page:
Next Playwright waits for the email and clicks the extracted magic link. This then loads the restricted page:
How does it work?
MailSlurp creates a real email address during the test. The test invokes the waitForLatestEmail
method that waits until an email arrives. We then use the getEmailLinks
method to return a list of every link found in the email. If we debug the test we can see the email received by MailSlurp:
Conclusion
Next Auth email links are a great way to authenticate users. MailSlurp adds the ability to test your login end to end. It's free for personal use so check it out at mailslurp.com.