Installation
Start by installing the appropriate package for your framework.
npm install next-auth@beta
Setup Environment
The only environment variable that is mandatory is the AUTH_SECRET
. This is a random value used by the library to encrypt tokens and email
verification hashes. (See Deployment to learn more). You can generate one via the official Auth.js CLI running:
npx auth secret
This will also add it to your .env
file, respecting the framework conventions (eg.: Next.js’ .env.local
).
Configure
Next, create the Auth.js config file and object. This is where you can control the behaviour of the library and specify custom authentication logic, adapters, etc. We recommend all frameworks to create an auth.ts
file in the project. In this file we’ll pass in all the options to the framework specific initalization function and then export the route handler(s), signin and signout methods, and more.
You can name this file whatever you want and place it wherever you like, these are just conventions we’ve come up with.
- Start by creating a new
auth.ts
file at the root of your app with the following content.
import NextAuth from "next-auth"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [],
})
- Add a Route Handler under
/app/api/auth/[...nextauth]/route.ts
.
This file must be an App Router Route Handler, however, the rest of your app
can stay under page/
if you’d like.
import { handlers } from "@/auth" // Referring to the auth.ts we just created
export const { GET, POST } = handlers
- Add optional Middleware to keep the session alive, this will update the session expiry every time its called.
export { auth as middleware } from "@/auth"
Authentication Methods
Next, let’s set up the authentication methods you want to use.
OAuth
Auth.js comes with over 80 providers preconfigured. We constantly test ~20 of the most popular ones, by having them enabled and actively used in our example application. You can choose a provider below to get a walk-through, or find your provider of choice in the sidebar for further details.
Or jump directly to one of the popular ones below.
This login mechanism starts by the user providing their email address at the login form. Then a Verification Token is sent to the provided email address. The user then has 24 hours to click the link in the email body to “consume” that token and register their account, otherwise the verification token will expire and they will have to request a new one.
An Email Provider can be used with both JSON Web Tokens and database session, but configuring a database is necessary so that Auth.js can save the verification tokens and look them up when the user attempts to login.
WebAuthn (Passkeys)
The WebAuthn/Passkeys provider is currently experimental and not supported by all framework integrations yet.
Install peer dependencies
This provider requires the @simplewebauthn/server
and @simplewebauthn/browser
packages to be installed.
npm install @simplewebauthn/server@9.0.3 @simplewebauthn/browser@9.0.1
The @simplewebauthn/browser
peer dependency is only required for custom signin pages. If you’re using the Auth.js default pages, you can skip installing that peer dependency.
Apply the required schema Migrations
This is the raw SQL migration for PostgreSQL, for more details including example migrations for other databases, check out the updated Prisma schemas at the @auth/prisma-adapter
docs.
In short, the Passkeys provider requires an additional table called Authenticator
.
-- CreateTable
CREATE TABLE "Authenticator" (
"credentialID" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"credentialPublicKey" TEXT NOT NULL,
"counter" INTEGER NOT NULL,
"credentialDeviceType" TEXT NOT NULL,
"credentialBackedUp" BOOLEAN NOT NULL,
"transports" TEXT,
PRIMARY KEY ("userId", "credentialID"),
CONSTRAINT "Authenticator_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "Authenticator_credentialID_key" ON "Authenticator"("credentialID");
Update Auth.js Configuration
Add the Passkeys
provider to your configuration. Also make sure you’re using a compatible database adapter.
import Passkey from "next-auth/providers/passkey"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export default {
adapter: PrismaAdapter(prisma),
providers: [Passkey],
experimental: { enableWebAuthn: true },
}
If you’re using the built-in Auth.js pages, then you are good to go! Navigating to your /signin
route should include a “Signin with Passkeys” button.
Custom Pages
If you’re using a custom signin page, you can leverage the next-auth
signIn
function to initiate WebAuthn registration and login flows with the following code.
When using the WebAuthn signIn
function, you’ll also need the
@simplewebauth/browser
peer dependency installed.
"use client"
import { useSession } from "next-auth/react"
import { signIn } from "next-auth/webauthn"
export default function Login() {
const { data: session, update, status } = useSession()
return (
<div>
{status === "authenticated" ? (
<button onClick={() => signIn("passkey", { action: "register" })}>
Register new Passkey
</button>
) : status === "unauthenticated" ? (
<button onClick={() => signIn("passkey")}>Sign in with Passkey</button>
) : null}
</div>
)
}
Credentials
To setup Auth.js with any external authentication mechanisms or use a traditional username/email and password flow, we can use the Credentials
provider. This provider is designed to forward any credentials inserted into the login form (i.e. username/password, but not limited to) to your authentication service.
The industry has come a long way since usernames and passwords as the go-to mechanism for authenticating and authorizing users to web applications. Therefore, if possible, we recommend a more modern and secure authentication mechanism such as any of the OAuth providers, Email Magic Links, or WebAuthn (Passkeys) options instead.
However, we also want to be flexible and support anything you deem appropriate for your application and use case, so there are no plans to remove this provider.
By default, the Credentials provider does not persist data in the database. However, you can still create and save any data in your database, you just have to provide the necessary logic, eg. to encrypt passwords, add rate-limiting, add password reset functionality, etc.
Credentials Provider
First, let’s initialize the Credentials
provider in the Auth.js configuration file. You’ll have to import the provider and add it to your providers
array.
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
// Your own logic for dealing with plaintext password strings; be careful!
import { saltAndHashPassword } from "@/utils/password"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Credentials({
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
credentials: {
email: {},
password: {},
},
authorize: async (credentials) => {
let user = null
// logic to salt and hash password
const pwHash = saltAndHashPassword(credentials.password)
// logic to verify if the user exists
user = await getUserFromDb(credentials.email, pwHash)
if (!user) {
// No user found, so this is their first attempt to login
// Optionally, this is also the place you could do a user registration
throw new Error("Invalid credentials.")
}
// return user object with their profile data
return user
},
}),
],
})
If you’re using TypeScript, you can augment the User
interface to match the
response of your authorize
callback, so whenever you read the user in other
callbacks (like the jwt
) the type will match correctly.
Signin Form
Finally, let’s create a simple sign-in form.
import { signIn } from "@/auth"
export function SignIn() {
return (
<form
action={async (formData) => {
"use server"
await signIn("credentials", formData)
}}
>
<label>
Email
<input name="email" type="email" />
</label>
<label>
Password
<input name="password" type="password" />
</label>
<button>Sign In</button>
</form>
)
}
Validating credentials
Always validate the credentials server-side, i.e. by leveraging a schema validation library like Zod.
npm install zod
Next, we’ll set up the schema and parsing in our auth.ts
configuration file, using the authorize
callback on the Credentials
provider.
import { object, string } from "zod"
export const signInSchema = object({
email: string({ required_error: "Email is required" })
.min(1, "Email is required")
.email("Invalid email"),
password: string({ required_error: "Password is required" })
.min(1, "Password is required")
.min(8, "Password must be more than 8 characters")
.max(32, "Password must be less than 32 characters"),
})
import NextAuth from "next-auth"
import { ZodError } from "zod"
import Credentials from "next-auth/providers/credentials"
import { signInSchema } from "./lib/zod"
// Your own logic for dealing with plaintext password strings; be careful!
import { saltAndHashPassword } from "@/utils/password"
import { getUserFromDb } from "@/utils/db"
export const { handlers, auth } = NextAuth({
providers: [
Credentials({
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
credentials: {
email: {},
password: {},
},
authorize: async (credentials) => {
try {
let user = null
const { email, password } = await signInSchema.parseAsync(credentials)
// logic to salt and hash password
const pwHash = saltAndHashPassword(password)
// logic to verify if the user exists
user = await getUserFromDb(email, pwHash)
if (!user) {
throw new Error("Invalid credentials.")
}
// return JSON object with the user data
return user
} catch (error) {
if (error instanceof ZodError) {
// Return `null` to indicate that the credentials are invalid
return null
}
}
},
}),
],
})
Database
Auth.js can persist sessions in a cookie. Using a database is therefore optional, unless you want to persist user data in your own database or a provider requires it.
Database adapters are the bridge we use to connect Auth.js to your database. For instance, when implementing magic links, the Email provider will require you to setup a database adapter to be able to store the verification tokens present on the links.
Official Adapters
Official database adapters are available under the @auth/
scope on npm. Their source code is available in the nextauthjs/next-auth
monorepo.
If you don’t find an adapter for your database or service of choice, you can create one yourself. Have a look at our guide on how to create a database adapter. If you create a new adapter, we’d love it if you opened a PR to share it with everyone!
Models
This is a generic ER Diagram of what the full database schema should look like. Your database adapter of choice will include a template schema with more details for applying this schema to the underlying database. For more details, check out our database models documentation. Please note, that the entire schema is not required for every use-case, for more details check out our database adapters guide.
Signing in
TODO:
Signing out
TODO:
Session management
TODO:
Protecting resources
TODO:
TypeScript
Auth.js is fully authored in TypeScript. It comes with its own type definitions to use in your project.
Even if you don’t use TypeScript, IDEs like VS Code will pick this up to provide you with a better developer experience. While you are typing, you will get suggestions about what certain objects/functions look like, and sometimes links to documentation, examples, and other valuable resources.
Philosophy
We have chosen module augmentation over generics as the main technique to type Auth.js resources across your application in case you extend them.
Why not use generics?
The interfaces that are shared across submodules are not passed to Auth.js library functions as generics.
Whenever these types are used, the functions always expect to return these formats. With generics, one might be able to override the type in one place, but not the other, which would cause the types to be out of sync with the implementation.
With module augmentation, you defined the types once, and you can be sure that they are always the same where it’s expected.
Module Augmentation
Auth.js libraries come with certain interfaces that are shared across submodules and different Auth.js libraries (For example: next-auth
and @auth/prisma-adapter
will rely on types from @auth/core
).
Good examples of such interfaces are Session
or User
. You can use TypeScript’s Module
Augmentation to extend these types to add your own properties across Auth.js without having to pass generic all over the place.
Let’s look at extending Session
for example.
import NextAuth, { type DefaultSession } from "next-auth"
declare module "next-auth" {
/**
* Returned by `auth`, `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
user: {
/** The user's postal address. */
address: string
/**
* By default, TypeScript merges new interface properties and overwrites existing ones.
* In this case, the default session user properties will be overwritten,
* with the new ones defined above. To keep the default session user properties,
* you need to add them back into the newly declared interface.
*/
} & DefaultSession["user"]
}
}
export const { auth, handlers } = NextAuth({
callbacks: {
session({ session, token, user }) {
// `session.user.address` is now a valid property, and will be type-checked
// in places like `useSession().data.user` or `auth().user`
return {
...session,
user: {
...session.user,
address: user.address,
},
}
},
},
})
Module augmentation is not limited to specific interfaces. You can augment any interface
we’ve defined, here are some of the more common interfaces that you might want to override based on your use case.
declare module "next-auth" {
/**
* The shape of the user object returned in the OAuth providers' `profile` callback,
* or the second parameter of the `session` callback, when using a database.
*/
interface User {}
/**
* The shape of the account object returned in the OAuth providers' `account` callback,
* Usually contains information about the provider being used, like OAuth tokens (`access_token`, etc).
*/
interface Account {}
/**
* Returned by `useSession`, `auth`, contains information about the active session.
*/
interface Session {}
}
// The `JWT` interface can be found in the `next-auth/jwt` submodule
import { JWT } from "next-auth/jwt"
declare module "next-auth/jwt" {
/** Returned by the `jwt` callback and `auth`, when using JWT sessions */
interface JWT {
/** OpenID ID Token */
idToken?: string
}
}
The module declaration can be added to any file that is
“included” in your
project’s tsconfig.json
.
Resources
- TypeScript documentation: Module Augmentation
- DigitalOcean: Module Augmentation in TypeScript
- Creating a Database Adapter
Deployment
Environment Variables
AUTH_SECRET
: Used to encrypt sessions, hash tokens, etc. You can generate one using the following command:
npm exec auth secret
You can rotate your secret without invalidating active sessions, by also
setting AUTH_SECRET_1
up to AUTH_SECRET_3
. The lowest number will be
preferred. Once you are confident all new sessions use the new secret, you can
remove the old one.
-
AUTH_URL
: You may set this to a full URL - including the path up to your handlers, eg.:/api/auth
- in case you host behind a reverse proxy, or similar, for example,AUTH_URL=http://localhost:3000/web/auth
. When set,AUTH_TRUST_HOST
is unnecessary. -
AUTH_TRUST_HOST
: Set totrue
so Auth.js can trust the hosting provider to santizize thehost
,x-forwarded-host
andx-forwarded-proto
headers.Auth.js will try to detect the if you host on a trustable hosting provider, in which case you don’t need to set this variable. Currently Vercel (
VERCEL
) and Cloudflare (CF_PAGES
) are supported. -
AUTH_REDIRECT_PROXY_URL
: See Securing a multiple deployments for more information.Providers relying on
response_mode=form_post
(like Apple) do not support this setting.
If you are using an OAuth Provider, your provider will provide you with a Client ID and Client Secret that you will need to set as environment variables as well (in the case of an OIDC provider, like Auth0, a third issuer
value might be also required, refer to the provider’s specific documentation).
Auth.js supports environment variable inference, meaning that if you name your provider environment variables following a specific syntax, you won’t need to explicitly pass them to the providers in your configuration.
Client ID’s and client secrets should be named AUTH_[PROVIDER]_ID
and AUTH_[PROVIDER]_SECRET
. If your provider requires an issuer, that should be named AUTH_[PROVIDER]_ISSUER
. For example:
AUTH_OKTA_ID=abc
AUTH_OKTA_SECRET=abc
AUTH_OKTA_ISSUER=abc
For more information, check out our environment variables page.
For consistency, we recommend prefixing all Auth.js environment variables with
AUTH_
. This way we can better autodetect them, and they can also be
distinguished from other environment variables more easily.
Serverless
- Create the required environment variables for your desired environments. Don’t forget to also add the required environment variables for your provider(s) of choice (i.e. OAuth
clientId
/clientSecret
, etc.). - When using an OAuth provider, make sure the callback URL for your production URL is setup correctly. Many OAuth providers will only allow you to set 1
callbackUrl
per OAuth application. In which case, you’ll need to create separate applications for each environment (development, production, etc.). Other providers, like Google, allow you to add manycallbackUrl
s to one application.- By default, the callbackUrl for
next-auth
(Next.js) applications should look something like this:https://company.com/api/auth/callback/[provider]
(replacecompany.com
with your domain andprovider
with the provider name, i.e.github
). - All other frameworks (
@auth/sveltekit
,@auth/express
, etc.), by default, will use the path/auth/callback/[provider]
.
- By default, the callbackUrl for
- Deploy! After having setup those two prerequisites, you should be able to deploy and run your Auth.js application on Netlify, Vercel, etc.
If you are storing users in a database, we recommend using a different OAuth app for development/production so that you don’t mix your test and production user base.
Self-hosted
Auth.js can also be deployed anywhere you can deploy your framework of choice. Check out the framework’s documentation on self-hosting.
- Docker: In a Docker environment, make sure to set either
trustHost: true
in your Auth.js configuration or theAUTH_TRUST_HOST
environment variable totrue
.
Our example application is also hosted via Docker here (see the source code). Below is an example Dockerfile
for a Next.js application using Auth.js.
# syntax=docker/dockerfile:1
# syntax=docker/dockerfile:1
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies
COPY package.json pnpm-lock.yaml* ./
RUN corepack enable pnpm && pnpm i --frozen-lockfile
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN corepack enable pnpm && pnpm build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD ["node", "server.js"]
Securing multiple deployments
OAuth providers in general cannot be configured to allow multiple callback URLs or using a wildcard.
However, Auth.js supports multiple deployments, even with most OAuth providers. The idea is to have one deployment which proxies authentication requests to the dynamic URLs of your main application. So you could have 1 stable deployment, like at auth.company.com
where you would point all your OAuth provider’s callbackUrl
s, and this application would then, upon successful authentication, redirect the user back to the preview deploy URL, like https://git-abc123-myapp.vercel.app
. Follow these steps to get started with securing preview deploys with Auth.js.
A common use case is to use the same OAuth client for all preview deployments.
Here is how to achieve this:
- Determine a stable deployment URL. For example, a deployment whose URL does not change between builds, for example.
auth.yourdomain.com
(using a subdomain is not a requirement, this can be the main site’s URL too, for example.) - Set
AUTH_REDIRECT_PROXY_URL
in all deployments to the stable URL, including the path from where Auth.js handles the routes. Eg.: (https://auth.yourdomain.com/api/auth
). - Point the callback/redirect URL in your OAuth provider’s configuration to use the stable deployment URL. For example, for GitHub it would be
https://auth.yourdomain.com/api/auth/callback/github
. - Make sure that
AUTH_SECRET
is the same for all deployments that need to support the same OAuth credentials
How does this work?
To support multiple deployments, Auth.js requires a deployment URL that is stable across deploys as a redirect proxy server.
It will redirect the OAuth callback request to the stable deployment URL, but only when the AUTH_REDIRECT_PROXY_URL
environment variable is set.
When a user initiates an OAuth sign-in flow on a configured deployment URL, it saves its URL in the state
- encrypted - query parameter but set the redirect_uri
to the stable deployment.
Then, the OAuth provider will redirect the user to the stable URL mentioned above, which will verify the state
parameter and redirect the user to the original URL if the state
is valid. This is secured by relying on the same server-side AUTH_SECRET
for the stable deployment and the other deployments.
Observability
To pass on your current user’s details on to your observability tools, you can use the callbacks provided by Auth.js. For example, in the session
callback, you could pass the user.id
on to Sentry.
import * as Sentry from "@sentry/browser"
import NextAuth from "next-auth"
export const { handlers, auth, signIn, signOut } = NextAuth({
callbacks: {
session({ session, user }) {
const scope = Sentry.getCurrentScope()
scope.setUser({
id: user.id,
email: user.email,
})
return session
},
},
})
Congratulations
You have learnt the basics of Auth.js and successfully configured it in your application.