---
title: Setup Fullstack Authentication with Next.js, Auth.js, and Cloudflare D1 · Cloudflare Developer Spotlight
description: In this tutorial, you will build a Next.js app with authentication powered by Auth.js, Resend, and Cloudflare D1.
lastUpdated: 2025-04-22T15:49:46.000Z
source_url:
  html: https://77261bbd.preview.developers.cloudflare.com/developer-spotlight/tutorials/fullstack-authentication-with-next-js-and-cloudflare-d1/index.md
  md: https://77261bbd.preview.developers.cloudflare.com/developer-spotlight/tutorials/fullstack-authentication-with-next-js-and-cloudflare-d1/
---

In this tutorial, you will build a [Next.js app](https://developers.cloudflare.com/workers/frameworks/framework-guides/nextjs/) with authentication powered by Auth.js, Resend, and [Cloudflare D1](https://developers.cloudflare.com/d1/).

Before continuing, make sure you have a Cloudflare account and have installed and [authenticated Wrangler](https://developers.cloudflare.com/workers/wrangler/commands/#login). 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](https://github.com/mackenly/auth-js-d1-example).

## Prerequisites

1. Sign up for a [Cloudflare account](https://dash.cloudflare.com/sign-up/workers-and-pages).
2. Install [`Node.js`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).

1) Create or login to a [Resend account](https://resend.com/signup) and get an [API key](https://resend.com/docs/dashboard/api-keys/introduction#add-api-key).
2) [Install and authenticate Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/).

## 1. Create a Next.js app using Workers

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

* npm

  ```sh
  npm create cloudflare@latest -- auth-js-d1-example --framework=next
  ```

* pnpm

  ```sh
  pnpm create cloudflare@latest auth-js-d1-example --framework=next
  ```

* yarn

  ```sh
  yarn create cloudflare auth-js-d1-example --framework=next
  ```

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](https://opennext.js.org/cloudflare) that will run in a Worker using [Workers Static Assets](https://developers.cloudflare.com/workers/frameworks/framework-guides/nextjs/#static-assets).

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
  }
}
```

Throughout this tutorial, we'll add several values to Cloudflare Secrets. For [local development](https://developers.cloudflare.com/workers/configuration/secrets/#local-development-with-secrets), 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.

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

Manually set URL

Within the Workers environment, the `AUTH_URL` doesn't always get picked up automatically by Auth.js, hence why we're specifying it manually here (we'll need to do the same for prod later).

## 2. Install Auth.js

Following the [installation instructions](https://authjs.dev/getting-started/installation?framework=Next.js) from Auth.js, begin by installing Auth.js:

* npm

  ```sh
  npm i next-auth@beta
  ```

* pnpm

  ```sh
  pnpm add next-auth@beta
  ```

* yarn

  ```sh
  yarn add next-auth@beta
  ```

Now run the following to generate an `AUTH_SECRET`:

```sh
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](https://developers.cloudflare.com/workers/configuration/secrets/#adding-secrets-to-your-project) by running the following and completing the steps to add a secret's value that we just generated:

```sh
npx wrangler secret put AUTH_SECRET
```

If you have not deployed yet that's fine. Allow wrangler to create the worker for you.

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

```sh
# ...
AUTH_SECRET = "<replace-me>"
# ...
```

Next, go into `cloudflare-env.d.ts` file and add the following to the CloudflareEnv interface:

```ts
interface CloudflareEnv {
  AUTH_SECRET: string;
}
```

## 3. Install Cloudflare D1 Adapter

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

* npm

  ```sh
  npm i @auth/d1-adapter
  ```

* pnpm

  ```sh
  pnpm add @auth/d1-adapter
  ```

* yarn

  ```sh
  yarn add @auth/d1-adapter
  ```

Create a D1 database using the following command:

```sh
npx wrangler d1 create auth-js-d1-example-db
```

When finished you should see instructions to add the database binding to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). Example binding:

* wrangler.jsonc

  ```jsonc
  {
    "d1_databases": [
      {
        "binding": "DB",
        "database_name": "auth-js-d1-example-db",
        "database_id": "<unique-ID-for-your-database>"
      }
    ]
  }
  ```

* wrangler.toml

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

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

```ts
interface CloudflareEnv {
  DB: D1Database;
  AUTH_SECRET: string;
}
```

## 4. Configure Credentials Provider

Auth.js provides integrations for many different [credential providers](https://authjs.dev/getting-started/authentication) such as Google, GitHub, etc. For this tutorial we're going to use [Resend for magic links](https://authjs.dev/getting-started/authentication/email). You should have already created a Resend account and have an [API key](https://resend.com/docs/dashboard/api-keys/introduction#add-api-key).

Using either a [Resend verified domain email address](https://resend.com/docs/dashboard/domains/introduction) or `onboarding@resend.dev`, add a new Secret to your Worker containing the email your magic links will come from:

```sh
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:

```sh
# ...
AUTH_EMAIL_FROM = "onboarding@resend.dev"
# ...
```

Now [create a Resend API key](https://resend.com/docs/dashboard/api-keys/introduction) with `Sending access` and add it to your Worker's Secrets:

```sh
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:

```sh
# ...
AUTH_RESEND_KEY = "<replace-me>"
# ...
```

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

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

* JavaScript

  ```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({ async: true })).env
            .AUTH_RESEND_KEY,
          from: (await getCloudflareContext({ async: true })).env.AUTH_EMAIL_FROM,
        }),
      ],
      adapter: D1Adapter((await getCloudflareContext({ async: true })).env.DB),
    });
  };


  export const { handlers, signIn, signOut, auth } = await authResult();
  ```

* TypeScript

  ```ts
  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 (): Promise<NextAuthResult> => {
    return NextAuth({
      providers: [
        Resend({
          apiKey: (await getCloudflareContext({async: true})).env.AUTH_RESEND_KEY,
          from: (await getCloudflareContext({async: true})).env.AUTH_EMAIL_FROM,
        }),
      ],
      adapter: D1Adapter((await getCloudflareContext({async: true})).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:

* JavaScript

  ```js
  import { handlers } from "../../../auth";


  export const { GET, POST } = handlers;
  ```

* TypeScript

  ```ts
  import { handlers } from "../../../auth";


  export const { GET, POST } = handlers;
  ```

Now, within the `src/` directory, create a `middleware.ts` file. If you do not have a `src/` directory, create a `middleware.ts` file in the root of your project. This will persist session data.

* JavaScript

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

* TypeScript

  ```ts
  export { auth as middleware } from "./app/auth";
  ```

## 5. Create Database Tables

The D1 adapter requires that tables be created within your database. It [recommends](https://authjs.dev/getting-started/adapters/d1#migrations) 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:

* JavaScript

  ```js
  import { up } from "@auth/d1-adapter";
  import { getCloudflareContext } from "@opennextjs/cloudflare";


  export async function GET() {
    try {
      await up((await getCloudflareContext({ async: true })).env.DB);
    } catch (e) {
      if (e instanceof Error) {
        const causeMessage =
          e.cause instanceof Error ? e.cause.message : String(e.cause);
        console.log(causeMessage, e.message);
      }
    }
    return new Response("Migration completed");
  }
  ```

* TypeScript

  ```ts
  import { up } from "@auth/d1-adapter";
  import { getCloudflareContext } from "@opennextjs/cloudflare";


  export async function GET() {
      try {
          await up((await getCloudflareContext({async: true})).env.DB)
      } catch (e: unknown) {
          if (e instanceof Error) {
              const causeMessage = e.cause instanceof Error ? e.cause.message : String(e.cause);
              console.log(causeMessage, 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.

Clean up

Running this multiple times won't hurt anything since the tables are only created if they do not already exist, but it's a good idea to remove this route from your production code once you've run it since you won't need it anymore.

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

## 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](https://ui.shadcn.com/):

```sh
npx shadcn@latest init -d
```

Next, run the following to add a few components:

```sh
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:

```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({async: true})).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:

* npm

  ```sh
  npm run preview
  ```

* pnpm

  ```sh
  pnpm run preview
  ```

* yarn

  ```sh
  yarn run preview
  ```

Windows support

OpenNext has [limited Windows support](https://opennext.js.org/cloudflare#windows-support) and recommends using WSL2 if developing on Windows.

Also, you may need to comment out the `@import "tw-animate-css"` line in the `globals.css` file.

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:

* npm

  ```sh
  npm run deploy
  ```

* pnpm

  ```sh
  pnpm run deploy
  ```

* yarn

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

```sh
npx wrangler secret put AUTH_URL
```

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

D1 Database Creation

You will need to hit the `/api/setup` on your deployed URL to create the necessary tables in your D1 database. It will create 4 tables if they don’t already exist: `accounts`, `sessions`, `users`, and `verification_tokens`. If the `api/setup` route is not working, you can also initialize your tables manually. Look in [migrations.ts](https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-d1/src/migrations.ts) of the Auth.js D1 adapter for the relevant SQL.

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

## Related resources

To build more with Workers, refer to [Tutorials](https://developers.cloudflare.com/workers/tutorials/).

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

* [Auth.js](https://authjs.dev/getting-started)
* [Resend](https://resend.com/)
* [Cloudflare D1](https://developers.cloudflare.com/d1/)

If you have any questions, need assistance, or would like to share your project, join the Cloudflare Developer community on [Discord](https://discord.cloudflare.com) to connect with other developers and the Cloudflare team.
