Skip to content

Setup Fullstack Authentication with Next.js, Auth.js, and Cloudflare D1

Last reviewed: 18 days ago

Developer Spotlight community contribution

Written by: Mackenly Jones

Profile: GitHub

In this tutorial, you will build a Next.js app with authentication powered by Auth.js, Resend, and Cloudflare D1.

Before continuing, make sure you have a Cloudflare account and have installed and authenticated Wrangler. Some experience with HTML, CSS, and JavaScript/TypeScript is helpful but not required. In this tutorial, you will learn:

  • How to create a Next.js application and run it on Cloudflare Workers
  • How to bind a Cloudflare D1 database to your Next.js app and use it to store authentication data
  • How to use Auth.js to add serverless fullstack authentication to your Next.js app

You can find the finished code for this project on GitHub.

Prerequisites

  1. Sign up for a Cloudflare account.
  2. Install Node.js.

Node.js version manager

Use a Node version manager like Volta or nvm to avoid permission issues and change Node.js versions. Wrangler, discussed later in this guide, requires a Node version of 16.17.0 or later.

  1. Create or login to a Resend account and get an API key.
  2. Install and authenticate Wrangler.

1. Create a Next.js app using Workers

From within the repository or directory where you want to create your project run:

Terminal window
npm create cloudflare@latest -- auth-js-d1-example --framework=next --experimental

For setup, select the following options:

  • For What would you like to start with?, choose Framework Starter.
  • For Which development framework do you want to use?, choose Next.js.
  • Complete the framework's own CLI wizard.
  • For Do you want to use git for version control?, choose Yes.
  • For Do you want to deploy your application?, choose No (we will be making some changes before deploying).

This will create a new Next.js project using OpenNext that will run in a Worker using Workers Static Assets.

Before we get started, open your project's tsconfig.json file and add the following to the compilerOptions object to allow for top level await needed to let our application get the Cloudflare context:

tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
}
}

Throughout this tutorial, we'll add several values to Cloudflare Secrets. For local development, add those same values to a file in the top level of your project called .dev.vars and make sure it is not committed into version control. This will let you work with Secret values locally. Go ahead and copy and paste the following into .dev.vars for now and replace the values as we go.

.dev.vars
AUTH_SECRET = "<replace-me>"
AUTH_RESEND_KEY = "<replace-me>"
AUTH_EMAIL_FROM = "onboarding@resend.dev"
AUTH_URL = "http://localhost:8787/"

2. Install Auth.js

Following the installation instructions from Auth.js, begin by installing Auth.js:

Terminal window
npm i next-auth@beta

Now run the following to generate an AUTH_SECRET:

Terminal window
npx auth secret

Now, deviating from the standard Auth.js setup, locate your generated secret (likely in a file named .env.local) and add the secret to your Workers application by running the following and completing the steps to add a secret's value that we just generated:

Terminal window
npx wrangler secret put AUTH_SECRET

After adding the secret, update your .dev.vars file to include an AUTH_SECRET value (this secret should be different from the one you generated earlier for security purposes):

.dev.vars
# ...
AUTH_SECRET = "<replace-me>"
# ...

Next, go into the newly generated env.d.ts file and add the following to the CloudflareEnv interface:

env.d.ts
interface CloudflareEnv {
AUTH_SECRET: string;
}

3. Install Cloudflare D1 Adapter

Now, install the Auth.js D1 adapter by running:

Terminal window
npm i @auth/d1-adapter

Create a D1 database using the following command:

Create D1 database
npx wrangler d1 create auth-js-d1-example-db

When finished you should see instructions to add the database binding to your wrangler.toml. Example binding:

wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "auth-js-d1-example-db"
database_id = "<unique-ID-for-your-database>"

Now, within your env.d.ts, add your D1 binding, like:

env.d.ts
interface CloudflareEnv {
DB: D1Database;
AUTH_SECRET: string;
}

4. Configure Credentials Provider

Auth.js provides integrations for many different credential providers such as Google, GitHub, etc. For this tutorial we're going to use Resend for magic links. You should have already created a Resend account and have an API key.

Using either a Resend verified domain email address or onboarding@resend.dev, add a new Secret to your Worker containing the email your magic links will come from:

Add Resend email to secrets
npx wrangler secret put AUTH_EMAIL_FROM

Next, ensure the AUTH_EMAIL_FROM environment variable is updated in your .dev.vars file with the email you just added as a secret:

.dev.vars
# ...
AUTH_EMAIL_FROM = "onboarding@resend.dev"
# ...

Now create a Resend API key with Sending access and add it to your Worker's Secrets:

Add Resend API key to secrets
npx wrangler secret put AUTH_RESEND_KEY

As with previous secrets, update your .dev.vars file with the new secret value for AUTH_RESEND_KEY to use in local development:

.dev.vars
# ...
AUTH_RESEND_KEY = "<replace-me>"
# ...

After adding both of those Secrets, your env.d.ts should now include the following:

env.d.ts
interface CloudflareEnv {
DB: D1Database;
AUTH_SECRET: string;
AUTH_RESEND_KEY: string;
AUTH_EMAIL_FROM: string;
}

Credential providers and database adapters are provided to Auth.js through a configuration file called auth.ts. Create a file within your src/app/ directory called auth.ts with the following contents:

src/app/auth.js
import NextAuth from "next-auth";
import { NextAuthResult } from "next-auth";
import { D1Adapter } from "@auth/d1-adapter";
import Resend from "next-auth/providers/resend";
import { getCloudflareContext } from "@opennextjs/cloudflare";
const authResult = async () => {
return NextAuth({
providers: [
Resend({
apiKey: (await getCloudflareContext()).env.AUTH_RESEND_KEY,
from: (await getCloudflareContext()).env.AUTH_EMAIL_FROM,
}),
],
adapter: D1Adapter((await getCloudflareContext()).env.DB),
});
};
export const { handlers, signIn, signOut, auth } = await authResult();

Now, lets add the route handler and middleware used to authenticate and persist sessions.

Create a new directory structure and route handler within src/app/api/auth/[...nextauth] called route.ts. The file should contain:

src/app/api/auth/[...nextauth]/route.js
import { handlers } from "../../../auth";
export const { GET, POST } = handlers;

Now, within the src/ directory, create a middleware.ts file to persist session data containing the following:

src/middleware.js
export { auth as middleware } from "./app/auth";

5. Create Database Tables

The D1 adapter requires that tables be created within your database. It recommends using the exported up() method to complete this. Within src/app/api/ create a directory called setup containing a file called route.ts. Within this route handler, add the following code:

src/app/api/setup/route.js
import { up } from "@auth/d1-adapter";
import { getCloudflareContext } from "@opennextjs/cloudflare";
export async function GET(request) {
try {
await up((await getCloudflareContext()).env.DB);
} catch (e) {
console.log(e.cause.message, e.message);
}
return new Response("Migration completed");
}

You'll need to run this once on your production database to create the necessary tables. If you're following along with this tutorial, we'll run it together in a few steps.

Before we go further, make sure you've created all of the necessary files:

  • Directorysrc/
    • Directoryapp/
      • Directoryapi/
        • Directoryauth/
          • Directory[...nextauth]/
            • route.ts
        • Directorysetup/
          • route.ts
      • auth.ts
      • page.ts
    • middleware.ts
  • env.d.ts
  • wrangler.toml

6. Build Sign-in Interface

We've completed the backend steps for our application. Now, we need a way to sign in. First, let's install shadcn:

Install shadcn
npx shadcn@latest init -d

Next, run the following to add a few components:

Add components
npx shadcn@latest add button input card avatar label

To make it easy, we've provided a basic sign-in interface for you below that you can copy into your app. You will likely want to customize this to fit your needs, but for now, this will let you sign in, see your account details, and update your user's name.

Replace the contents of page.ts from within the app/ directory with the following:

src/app/page.ts
import { redirect } from 'next/navigation';
import { signIn, signOut, auth } from './auth';
import { updateRecord } from '@auth/d1-adapter';
import { getCloudflareContext } from '@opennextjs/cloudflare';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Label } from '@/components/ui/label';
async function updateName(formData: FormData): Promise<void> {
'use server';
const session = await auth();
if (!session?.user?.id) {
return;
}
const name = formData.get('name') as string;
if (!name) {
return;
}
const query = `UPDATE users SET name = $1 WHERE id = $2`;
await updateRecord((await getCloudflareContext()).env.DB, query, [name, session.user.id]);
redirect('/');
}
export default async function Home() {
const session = await auth();
return (
<main className="flex items-center justify-center min-h-screen bg-background">
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl font-bold text-center">{session ? 'User Profile' : 'Login'}</CardTitle>
<CardDescription className="text-center">
{session ? 'Manage your account' : 'Welcome to the auth-js-d1-example demo'}
</CardDescription>
</CardHeader>
<CardContent>
{session ? (
<div className="space-y-4">
<div className="flex items-center space-x-4">
<Avatar>
<AvatarImage src={session.user?.image || ''} alt={session.user?.name || ''} />
<AvatarFallback>{session.user?.name?.[0] || 'U'}</AvatarFallback>
</Avatar>
<div>
<p className="font-medium">{session.user?.name || 'No name set'}</p>
<p className="text-sm text-muted-foreground">{session.user?.email}</p>
</div>
</div>
<div>
<p className="text-sm font-medium">User ID: {session.user?.id}</p>
</div>
<form action={updateName} className="space-y-2">
<Label htmlFor="name">Update Name</Label>
<Input id="name" name="name" placeholder="Enter new name" />
<Button type="submit" className="w-full">
Update Name
</Button>
</form>
</div>
) : (
<form
action={async (formData) => {
'use server';
await signIn('resend', { email: formData.get('email') as string });
}}
className="space-y-4"
>
<div className="space-y-2">
<Input
type="email"
name="email"
placeholder="Email"
autoCapitalize="none"
autoComplete="email"
autoCorrect="off"
required
/>
</div>
<Button className="w-full" type="submit">
Sign in with Resend
</Button>
</form>
)}
</CardContent>
{session && (
<CardFooter>
<form
action={async () => {
'use server';
await signOut();
Response.redirect('/');
}}
>
<Button type="submit" variant="outline" className="w-full">
Sign out
</Button>
</form>
</CardFooter>
)}
</Card>
</main>
);
}

7. Preview and Deploy

Now, it's time to preview our app. Run the following to preview your application:

Terminal window
npm run preview

You should see our login form. But wait, we're not done yet. Remember to create your database tables by visiting /api/setup. You should see Migration completed. This means your database is ready to go.

Navigate back to your application's homepage. Enter your email and sign in (use the same email as your Resend account if you used the onboarding@resend.dev address). You should receive an email in your inbox (check spam). Follow the link to sign in. If everything is configured correctly, you should now see a basic user profile letting your update your name and sign out.

Now let's deploy our application to production. From within the project's directory run:

Terminal window
npm run deploy

This will build and deploy your application as a Worker. Note that you may need to select which account you want to deploy your Worker to. After your app is deployed, Wrangler should give you the URL on which it was deployed. It might look something like this: https://auth-js-d1-example.example.workers.dev. Add your URL to your Worker using:

Add URL to secrets
npx wrangler secret put AUTH_URL

After the changes are deployed, you should now be able to access and try out your new application.

You have successfully created, configured, and deployed a fullstack Next.js application with authentication powered by Auth.js, Resend, and Cloudflare D1.

To build more with Workers, refer to Tutorials.

Find more information about the tools and services used in this tutorial at:

If you have any questions, need assistance, or would like to share your project, join the Cloudflare Developer community on Discord to connect with other developers and the Cloudflare team.