NextAuth.js FAQ & Answers
1779 expert NextAuth.js answers researched from official documentation. Every answer cites authoritative sources you can verify.
Jump to section:
OAuth Providers > Apple Provider
40 questionsYes, PKCE is enabled by default for the Apple provider (checks: ["pkce"]).
Apple requires the client secret to be a JWT (JSON Web Token).
null (Apple doesn't provide profile pictures through their authentication flow)
Service ID. The clientId should be set to your Apple Service ID, not your App ID.
transfer_sub (a string value representing the transfer identifier used to migrate users)
No, Apple doesn't allow you to use localhost in domains or subdomains.
Yes, developers must register their outbound email domains with the Private Relay service, and all registered domains must create Sender Policy Framework (SPF) DNS TXT records.
6 months (180 days). Apple doesn't accept client secret JWTs with an expiration date more than six months after the creation date.
is_private_email (a String or Boolean value: "true"/"false" or true/false)
teamId (Team ID), keyId (Key ID), privateKey (contents of .p8 file), and clientId (Service ID)
With form_post, parameters are sent back in the body of the POST request. With query, parameters are in the URL. However, Apple requires form_post when requesting name or email scope, and query mode causes scope not to work.
AUTH_APPLE_ID and AUTH_APPLE_SECRET (it adds them to your .env file)
form_post. The response_mode must be form_post when name or email scope is requested.
Always true (or the string "true"), because Apple servers only return verified email addresses.
Setting pkceCodeVerifier cookie with sameSite: 'none' and secure: true in the cookies configuration
Yes, Apple requires all sites to run HTTPS, including local development instances.
nonce_supported. If this claim returns true, you should treat nonce as mandatory and fail the transaction; otherwise, you can proceed treating the nonce as optional.
Only the first time the user consents to the app. Apple only returns the name on the first sign in.
An Integer value (0/Unsupported, 1/Unknown, 2/LikelyReal) that indicates whether the user appears to be a real person. It's only present on iOS 14+, macOS 11+, watchOS 7+, tvOS 14+, and is NOT available for web-based apps.
Email Authentication > Email Services
40 questionsThe default maximum rate limit is 2 requests per second. After that, you'll hit the rate limit and receive a 429 response error code. This number can be increased for trusted senders upon request.
The sendVerificationRequest function receives: identifier (email address), url (verification link), provider (containing server and from properties), theme (UI theme options), token (verification token), and expires (expiration date).
Port 587 should use secure: false and starts unencrypted, then upgrades via STARTTLS. Most modern SMTP servers support STARTTLS automatically unless explicitly disabled with ignoreTLS: true.
Postmark requires that you keep your bounce rate below 10% and spam complaint rate below 0.1%.
The SendGrid provider automatically detects the AUTH_SENDGRID_KEY environment variable. If you name your environment variable this way, the provider will pick it up automatically.
The batch endpoint accepts up to 500 messages per API call. The total payload size, including attachments, is limited to 50 MB for the Batch Email API. For the regular Email API, the size limit is 10 MB including attachments.
The default colors are: background: '#f9f9f9', text: '#444', mainBackground: '#fff', buttonBackground: brandColor, buttonBorder: brandColor, and buttonText: theme.buttonText || '#fff'.
SendGrid has a default rate limit of 600 requests per minute for most API endpoints. When exceeded, the API responds with an HTTP 429 status code and a Retry-After header.
The maxAge parameter is measured in seconds. The default value is 24 * 60 * 60 (86,400 seconds = 24 hours). You can configure it like: maxAge: 24 * 60 * 60 // How long email links are valid for (default 24h)
By default, NextAuth.js normalizes email addresses by treating them as case-insensitive (technically not compliant to RFC 2821 spec) and removes any secondary email addresses passed in as comma-separated lists. The documentation states this approach 'causes more problems than it solves' when strictly following RFC 2821.
The default expiration time is 24 hours (86,400 seconds). The documentation states: 'By default, the email verification token in NextAuth.js is valid for 24 hours.'
No, NextAuth.js does not include nodemailer as a dependency. You must install it separately by running npm install nodemailer or yarn add nodemailer if you want to use the Nodemailer provider.
The default method uses randomBytes(32).toString('hex'), generating a 32-byte random token converted to hexadecimal format.
If requireTLS is true and secure is false, it forces Nodemailer to use STARTTLS even if the server does not advertise support for it.
The total size of your email, including attachments, must be less than 30MB. The total number of recipients must be no more than 1000.
Yes, you can apply your own normalization via the normalizeIdentifier method on the EmailProvider. The function signature is: normalizeIdentifier: (identifier) => string. This allows you to customize email validation and normalization logic.
Use the format 'Your App Name <[email protected]>'. Example: Resend({ from: 'Your App Name <[email protected]>' })
The Postmark provider automatically detects the AUTH_POSTMARK_KEY environment variable. If you name your environment variable this way, the provider will pick it up automatically.
Port 465 should use secure: true. The documentation states: 'The secure option should be set to true only for port 465. For every other port, it should be false.'
The default subject line is Sign in to ${host}, where ${host} is dynamically replaced with the hostname extracted from the sign-in URL.
The default brand color is #346df1 if not specified in the theme configuration.
Postmark uses the X-Postmark-Server-Token header for authentication and authorization.
The required fields are: identifier (email address), token (unique verification token), expires (expiration timestamp), and a composite unique constraint on [identifier, token]. Timestamps createdAt and updatedAt are also typically included.
Your complaint rate must be lower than 0.08% and your bounce rate must be lower than 4%.
SendGrid offers a free tier which allows sending up to 100 emails per day.
The signIn() callback is triggered twice: first when the user makes a Verification Request (before they are sent an email), and again after they activate the link in the sign-in email.
The Postmark API endpoint for sending emails is https://api.postmarkapp.com/email.
Auth.js has built-in HTTP Email providers for Resend, SendGrid, and Postmark. These providers make it easy to integrate these services for passwordless magic link authentication.
The SendGrid API endpoint is https://api.sendgrid.com (example: https://api.sendgrid.com/v3/templates).
A user account will not be created until the first time they verify their email address by clicking the link in the email. The documentation states: 'A user account (an entry in the Users table) will not be created until the first time they verify their email address.'
Yes, a database adapter is required. The email provider cannot be used without a database because verification tokens need to be stored. As the documentation states: 'The email provider requires a database, it cannot be used without one.'
If ignoreTLS is true and secure is false, TLS will not be used (either to connect, or as a STARTTLS connection upgrade command). This disables encryption entirely.
Resend uses Bearer token authentication with the apiKey in the Authorization header (format: Authorization: Bearer YOUR_API_KEY).
For SMTP, Postmark recommends limiting the total number of connections to 10 concurrent connections per IP. Connections can be reused, and more than one message can be submitted within the same connection.
The connection string format is: smtp://username:[email protected]:587. For example: EMAIL_SERVER=smtp://username:[email protected]:587
The Resend provider automatically detects the AUTH_RESEND_KEY environment variable. If you name your environment variable this way, the provider will pick it up automatically without needing to pass it manually.
You can set theme.colorScheme to 'auto' (follow preferred system theme), 'dark' (force dark mode), or 'light' (force light mode).
You can configure: signIn (custom sign-in page), signOut (sign-out page), error (error page), verifyRequest (check email message page), and newUser (new user page). Example: pages: { signIn: '/auth/signin', verifyRequest: '/auth/verify-request', error: '/auth/error' }
Database Adapters > Custom Adapters
39 questionsAdapters must return null when a record is not found (e.g., getUser, getUserByAccount, getAuthenticator). Adapters must throw an error when an operation fails (e.g., updateAuthenticatorCounter fails, createAuthenticator fails).
linkAccount accepts an account parameter containing the account information to be linked to a user. In modern versions, it accepts an object instead of multiple individual parameters.
The authenticator methods (createAuthenticator, getAuthenticator, listAuthenticatorsByUserId, updateAuthenticatorCounter) are optional and only needed for WebAuthn authentication flows.
Use the JSDoc comment: /** @return { import("next-auth/adapters").Adapter } */ above your adapter function to get helpful editor hints and auto-completion.
The function is called runBasicTests and it runs the most basic tests that all adapters should conform to and pass.
createVerificationToken returns Awaitable<undefined | null | VerificationToken>.
createVerificationToken is optional. It's only needed when using email-based authentication flows.
The created_at/createdAt and updated_at/updatedAt fields are removed from all Models in NextAuth version 4.
- createAuthenticator - creates a new authenticator, 2) getAuthenticator - retrieves an authenticator by credentialID, 3) listAuthenticatorsByUserId - returns all authenticators for a user, 4) updateAuthenticatorCounter - updates the authenticator's counter on each login.
getSessionAndUser returns Awaitable<{ session: AdapterSession; user: AdapterUser } | null>. It returns both the session and user from the database in one go, or null if either could not be retrieved.
You can extend an existing adapter by spreading it and adding your custom methods: adapter: { ...PrismaAdapter(prisma), createUser() { /* custom implementation */ } }
The Session model in NextAuth.js v4 has the following fields: id, userId, expires, and sessionToken.
The code can be found in basic-tests.ts in the nextauthjs/adapters repository, and specific implementations can be found in the /tests subdirectory.
updateSession returns Awaitable<undefined | null | AdapterSession>. It should return the updated session object after successfully updating it in the database.
Import the Adapter type from "next-auth/adapters" using: import type { Adapter } from "next-auth/adapters"
useVerificationToken returns the verification token from the database and deletes it so it can only be used once.
unlinkAccount accepts an object with { providerAccountId, provider } parameters.
getUserByAccount accepts an object with two parameters: { providerAccountId, provider }. It uses the provider id and the id of the user for a specific account to get the user.
The function pattern allows you to instantiate a client/ORM with the provided config parameter, making it easier to configure database connections, reuse existing client instances, and support multiple database configurations.
Yes. The token provided to createVerificationToken is already hashed, so nothing has to be done - simply write it to your database.
No. As of v4, NextAuth.js no longer ships with an adapter included by default. If you would like to persist any information, you need to install one of the many available adapters yourself.
createUser returns Awaitable<AdapterUser>. The method signature is: async createUser(user: Omit<AdapterUser, 'id'>): Promise<AdapterUser>
getUserByEmail returns null when no user is found. The return type is Awaitable<AdapterUser | null>.
- Plain object pattern: define adapter methods directly in an object. 2) Function pattern (recommended): a function that accepts config parameters and returns an adapter object. The function pattern is what official adapters use.
Awaitable is a type used in NextAuth to indicate that methods are asynchronous and can return either a Promise or the value directly. It's used throughout adapter method signatures to support both async and sync implementations.
AdapterUser requires: id: string, email: string, and emailVerified: Date | null
NextAuth.js currently requires 10 different methods to comprehensively cover each part of the sign in flow, each providing a different part for storing session and account details and getting those details.
If a user is not found, listAuthenticatorsByUserId should still return an empty array. If the retrieval fails for some other reason, the adapter must throw an error.
If an account is not found, the adapter must return null. The return type is Awaitable<AdapterUser | null>.
The VerificationToken interface contains: { identifier: string, expires: Date, token: string }
If any of the methods are not implemented, but are called by Auth.js, an error will be shown to the user and the operation will fail.
The adapter method getSession was renamed to getSessionAndUser in NextAuth v4.
The methods required for all sign in flows are: createUser, getUser, and getUserByEmail.
For NextAuth v4 compatibility, use adapters with the @next-auth/ prefix (e.g., @next-auth/prisma-adapter, @next-auth/mongodb-adapter). The newer @auth/* adapters are not compatible with the current stable version of next-auth (v4.22.1).
deleteSession returns Awaitable<undefined | null | AdapterSession>. It is preferred that this method also returns the session that is being deleted for logging purposes.
AdapterSession contains: { sessionToken: string, userId: string, expires: Date }
The emailVerified field indicates whether the user has verified their email address via an Email provider. It is null if the user has not signed in with the Email provider yet, or contains the date of the first successful signin.
OAuth Authentication > Token Handling
38 questionsThe update() method triggers a jwt callback with the trigger: 'update' option. You can use this to update the session object on the server.
Encrypted (JWE). By default tokens are not signed but are encrypted. JWT tokens are encrypted by default, not just signed.
You need prompt: 'consent', access_type: 'offline', and response_type: 'code' in the authorization.params configuration.
Yes, NEXTAUTH_SECRET is a required environment variable in production. If not set, NextAuth.js will throw a MissingSecretError: 'Please define a secret in production.'
86400 seconds (24 hours). This is calculated as 24 * 60 * 60 seconds.
__Secure-next-auth.session-token. The __Secure- prefix is used when NEXTAUTH_URL starts with https://.
NextAuth.js implements cookie chunking. If the session exceeds the limit, it creates additional cookies with the suffix .{number}, then merges them together when reading the value.
Yes. NextAuth.js sessions are rotating/rolling, meaning if the user is interacting with the site, the session won't expire.
Yes. NextAuth.js supports PKCE through the 'checks' configuration option. Set checks: ['pkce', 'state'] in your OAuth provider configuration to enable it.
A256CBC-HS512 (AES-256-CBC with HMAC-SHA512). The JWT issued by Auth.js is encrypted by default using the A256CBC-HS512 algorithm (JWE).
It returns the raw encrypted JWE (JSON Web Encryption) string without decrypting or verifying it. Without raw: true, it returns the decrypted token payload.
No. NextAuth.js does not currently handle Access Token rotation for OAuth providers automatically, however you can implement it using jwt and session callbacks.
Only if NEXTAUTH_SECRET is not set as an environment variable. If you don't have NEXTAUTH_SECRET set, you will have to pass your secret as the 'secret' parameter to getToken().
No. Google only provides a refresh token to an application the first time a user signs in. To force Google to re-issue a refresh token, the user needs to remove the application from their account at https://myaccount.google.com/permissions.
Set allowDangerousEmailAccountLinking: true in your provider configuration. This should only be used with providers you trust to verify email addresses.
It defaults to session.maxAge, which is 2592000 seconds (30 days). The value is 60 * 60 * 24 * 30 seconds.
Override authorization.params.scope in your provider configuration. For example: authorization: { params: { scope: 'openid email profile offline_access' } }
The session is updated every time. However, this option is ignored if using JSON Web Tokens (only applies to database sessions).
Yes. The account.expires_at field is automatically calculated and available during first-time login in the jwt callback, based on the expires_in value from the OAuth provider.
Yes. The getToken() helper can read and decode tokens passed from the Authorization: 'Bearer token' HTTP header. If the cookie is not found, it looks for a bearer token in the authorization header.
Database session is the default. If you use an adapter, NextAuth.js defaults the session strategy to 'database' instead of 'jwt'.
3936 bytes (4096 - 160 bytes). The ALLOWED_COOKIE_SIZE is 4096 bytes with an ESTIMATED_EMPTY_COOKIE_SIZE of 160 bytes.
Email addresses in OAuth accounts aren't necessarily verified. A bad actor could create an OAuth account using someone else's email and gain access to their existing account if automatic linking is enabled.
The jwt callback is invoked by requests to /api/auth/signin, /api/auth/session and calls to getSession(), getServerSession(), useSession() (only when using JWT sessions). It's called whenever a JWT is created (at sign in) or updated (whenever a session is accessed).
JWT is the default session strategy. JSON Web Tokens are enabled by default if you have not specified an adapter.
4096 bytes per cookie. This is a browser limitation - cookies have a limit of around 4096 bytes, and if a value exceeds it, the cookie won't be created.
No. Automatic account linking on sign in is not secure between arbitrary providers and is disabled by default.
A256GCM (AES-256 in Galois/Counter Mode). Since v4, all JWT tokens are encrypted by default with A256GCM.
Refresh tokens are usually only usable once. After a successful refresh, the refresh_token is invalidated and cannot be used again. NextAuth.js does not handle concurrent refresh token requests properly, which can cause race conditions.
2592000 seconds (30 days). This is calculated as 30 * 24 * 60 * 60 seconds.
NextAuth.js supports OAuth 1.0, 1.0A, 2.0 and OpenID Connect, with built-in support for most popular sign-in services.
The 'offline_access' scope. For example, with Okta: authorization: { params: { scope: 'openid email profile offline_access' } }. Note: Google uses access_type: 'offline' instead.
The account parameter is only available when the callback is being invoked for the first time (i.e., the user is being signed in). Subsequent invocations will only contain the token parameter.
NextAuth.js calculates it as Date.now() + refreshedTokens.expires_in * 1000, where expires_in is the number of seconds until expiration provided by the OAuth provider.
HKDF-SHA256 (HMAC-based Key Derivation Function with SHA-256). Keys are derived using HKDF-SHA256 to produce a 512-bit (64 byte) encryption key for A256CBC-HS512.
You can use 'openssl rand -base64 32' or visit https://generate-secret.vercel.app/32 to generate a random value.
next-auth.session-token (without the __Secure- prefix). The secure prefix is not used when NEXTAUTH_URL starts with http:// or is not set.
Error Handling
36 questionssignIn (on successful sign in), signOut (on signout), createUser (user created), updateUser (user updated), linkAccount (account linked to a user), and session (session is active).
It will throw an error. NextAuth.js requires NEXTAUTH_SECRET in production to encrypt JWTs and hash email verification tokens.
Enables debug messages for authentication and database operations.
In a folder outside /pages/api which is reserved for API code.
The email on the account is already linked, but not with this OAuth account.
For increased security. NextAuth.js purposefully restricts the returned error codes.
Related to the Email provider - the token has expired or has already been used.
Awaitable<string | boolean> - can return true, false, or a URL string
OAuthSignin, OAuthCallback, OAuthCreateAccount, EmailCreateAccount, Callback, OAuthAccountNotLinked, EmailSignin, and CredentialsSignin.
Return null. Throwing an error inside authorize causes a CallbackRouteError.
Configuration (server configuration problem), AccessDenied (occurs when access is restricted through the signIn or redirect callback), Verification (related to Email provider - token expired or already used), and Default (catch-all error).
No, events are asynchronous functions that do not return a response. They are useful for audit logs/reporting or handling side-effects.
No. Redirects returned by the signIn callback cancel the authentication flow and should only redirect to error pages. Use the callbackUrl option or redirect callback for post-authentication redirects.
To hash JWT tokens, sign and encrypt session cookies, and generate public/private keys.
error (string | undefined), status (number), ok (boolean - true if signin was successful), and url (string | null - null if there was an error).
It requires the URL to be an absolute URL at the same hostname, or a relative URL starting with a slash, otherwise it redirects to the homepage.
The authorize callback returned null in the Credentials provider.
Error in constructing an authorization URL for the OAuth provider.
Database Adapters > Drizzle Adapter
36 questionsuserId (not null, references users.id), type (not null), provider (not null), providerAccountId (not null), plus optional fields: refresh_token, access_token, expires_at, token_type, scope, id_token, session_state
After the user successfully signs in by clicking the email link and the tokens match and haven't expired
sessionToken (string), userId (string, references users.id), and expires (Date/timestamp)
Yes, MySQL varchar requires a length specification: varchar({ length: 255 })
id (primary key), name (nullable text), email (not null text), emailVerified (nullable timestamp), and image (nullable text)
drizzle-kit migrate or using the migrate() function programmatically
The compound primary key consists of userId and credentialID columns: primaryKey({ columns: [authenticator.userId, authenticator.credentialID] })
No, the length parameter is optional for PostgreSQL varchar according to PostgreSQL docs
The compound primary key consists of provider and providerAccountId columns: primaryKey({ columns: [account.provider, account.providerAccountId] })
The third parameter now requires an object with a columns array: primaryKey({ columns: [col1, col2] }) instead of primaryKey(col1, col2)
No, the sessionsTable is optional and only required if you're using the database session strategy.
credentialID (text, not null, unique), credentialPublicKey (text, not null), counter (integer, not null), userId (text, not null), providerAccountId (text, not null), credentialDeviceType (text, not null), credentialBackedUp (boolean, not null), and transports (text, optional)
A Drizzle database instance (db), which can be types like BaseSQLiteDatabase for SQLite or a database instance created with drizzle() for PostgreSQL/MySQL
The signature counter for WebAuthn authentication attempts to help prevent replay attacks
usersTable, accountsTable, sessionsTable, and verificationTokensTable
The compound primary key consists of identifier and token columns: primaryKey({ columns: [verificationToken.identifier, verificationToken.token] })
drizzle-kit must be installed as a dev dependency using: npm install drizzle-kit --save-dev or pnpm add drizzle-kit --save-dev
No, the verificationTokensTable is optional and only required if you're using a Magic Link provider.
Use .$defaultFn(() => crypto.randomUUID()) on the id field: id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID())
The first three tables use plural names (users, accounts, sessions) but verificationToken is singular
identifier (text, not null), token (text, not null), and expires (timestamp, not null)
import type { AdapterAccount } from '@auth/core/adapters' and use: type: text('type').$type<AdapterAccount['type']>().notNull()
Pass them as the second parameter: DrizzleAdapter(db, { usersTable: users, accountsTable: accounts, sessionsTable: sessions, verificationTokensTable: verificationTokens })
Migration v4 to v5
35 questionsAuth.js v5 now builds on @auth/core with stricter OAuth/OIDC spec-compliance, which might break some existing OAuth providers that don't fully conform to the specifications.
Set trustHost: true (or AUTH_TRUST_HOST=true environment variable) in Docker environments or when running Auth.js behind a proxy. This allows Auth.js to trust the X-Forwarded-Host and X-Forwarded-Proto headers to auto-detect the host URL.
Import the handlers from your auth.ts file and export GET and POST: import { handlers } from '@/auth'
export const { GET, POST } = handlers
Import from 'next-auth/react' (which is marked with 'use client') and wrap your component in SessionProvider: import { useSession, SessionProvider } from 'next-auth/react'. Client components using the hook must include the 'use client' directive.
export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [...] })
By default, the user is redirected to the current page after signing in. You can override this by setting the redirectTo option with a relative path.
In the config object, use: pages: { signIn: '/login' }. The key is the page type and the value is the path/route. You must ensure you actually have a page at the specified route.
The auth() function exported from your auth.ts config file replaces getServerSession. You import it with: import { auth } from '@/auth' and call it without passing authOptions: const session = await auth()
The authorize function now receives credentials through a destructured object: async authorize({ request }) instead of async authorize(credentials, req).
Both AUTH_URL and NEXTAUTH_URL are supported as aliases in v5 for backward compatibility. However, AUTH_URL is the recommended naming going forward and takes precedence if both are defined. AUTH_URL is mostly unnecessary in v5 as the host is inferred from request headers.
The oauth_token_secret and oauth_token fields can be removed from the account table if you are not using them.
signIn and signOut are exported from your centralized auth.ts config file: export const { auth, handlers, signIn, signOut } = NextAuth({ ... }). Import them with: import { signIn, signOut } from '@/auth' and use in server actions.
Import the auth function from your auth.ts config file and export it as middleware: import { auth } from '@/auth'
export const middleware = auth. The import next-auth/middleware from v4 is replaced.
The default updateAge is 24 hours (86,400 seconds) when using database-persisted sessions. This setting is ignored when using JWT strategy.
For OAuth providers, follow this format: AUTH_{PROVIDER}_{ID|SECRET}. For example: AUTH_GITHUB_ID and AUTH_GITHUB_SECRET will be automatically detected and used without explicit configuration.
AUTH_SECRET is the only environment variable that is really necessary. You do not need to additionally pass this value into your config as the secret configuration option if you've set the environment variable.
declare module '@auth/core' instead of 'next-auth' (from v4). For JWT types, use: declare module '@auth/core/jwt'.
When using the auth() function in v5, the session() callback is ignored. The auth() function will expose anything returned from the jwt() callback (or from the User if using a 'database' strategy) because auth() is always on the server.
No, installing @auth/core is not necessary. NextAuth v5 builds on @auth/core internally, and as a user you should never have to interact with @auth/core directly.
The default maxAge is 30 days (30 * 24 * 60 * 60 = 2,592,000 seconds).
pnpm: 'pnpm add next-auth@beta', yarn: 'yarn add next-auth@beta', bun: 'bun add next-auth@beta'
The configuration is now in a file named auth.ts in the root of your repository (or in the src folder if you have one), instead of in the API route pages/api/auth/[...nextauth].ts.
Adapter packages are renamed from @next-auth/-adapter to @auth/-adapter. For example: @next-auth/prisma-adapter becomes @auth/prisma-adapter.
NextAuth.js v5 can automatically infer environment variables that are prefixed with AUTH_ instead of NEXTAUTH_. For example: AUTH_GITHUB_ID, AUTH_GITHUB_SECRET, AUTH_URL, AUTH_SECRET.
No, NextAuth.js v5 does not introduce any breaking changes to the database schema.
The session cookie name changed from next-auth.session-token (v4) to authjs.session-token (v5).
Import CredentialsSignin and create a custom error class: import { CredentialsSignin } from 'next-auth'
class InvalidLoginError extends CredentialsSignin { code = 'Invalid identifier or password' }
Then throw new InvalidLoginError() in the authorize function.
Create a separate auth.config.ts file that exports a configuration object without database adapters or edge-incompatible code. Import this config in your middleware and use it to initialize Auth.js there, avoiding database adapter imports.
Only the 'jwt' session strategy is supported in Edge runtime. The database session strategy is not compatible with Edge runtime depending on the database driver package.
Run 'npx auth secret' in your project's root directory and it will autogenerate a random value and put it in your .env.local file. Alternatively, use 'openssl rand -base64 32' or https://generate-secret.vercel.app/32.
The default session strategy is 'jwt' (an encrypted JWT stored in the session cookie) when no adapter is configured.
When you use an adapter, it defaults to the 'database' session strategy instead of 'jwt'.
Configuration Options > Core Options
34 questionsWhen useSecureCookies is true, the cookie prefix is set to "__Secure-" for most cookies, and "__Host-" for the CSRF token cookie.
useSecureCookies defaults to true for URLs that start with https://, and defaults to false for URLs that start with http:// (e.g. http://localhost:3000).
No, if you are using the Essential Next.js Build Plugin within your project, you do not need to set the NEXTAUTH_URL environment variable as it is set automatically as part of the build process.
Yes, NextAuth.js uses encrypted JSON Web Tokens (JWE) by default.
The logger accepts three methods: error(code, metadata), warn(code), and debug(code, metadata).
No, the updateAge option is ignored if using JSON Web Tokens (JWT strategy). It only applies to database sessions.
You can run npx auth secret in your project's root, and it will autogenerate a random value and put it in your .env.local file.
The theme object supports: colorScheme ("auto" | "dark" | "light"), brandColor (hex color code), logo (absolute URL to image), and buttonText (hex color code).
The default cookie names are: "authjs.session-token" (for sessionToken), "authjs.callback-url" (for callbackUrl), and "authjs.csrf-token" or "__Host-authjs.csrf-token" (for csrfToken when using secure cookies).
When using a custom base path, you need to pass the basePath page prop to the SessionProvider component on the client side.
The default value is false. Automatic account linking is disabled by default for security reasons.
The providers option is required. It must be an array of authentication providers (e.g. Google, Facebook, Twitter, GitHub, Email, etc) in any order.
The callbackUrl defaults to the page URL the sign-in is initiated from.
When you use an adapter, the session strategy defaults to "database" instead of "jwt".
The user, account, and profile parameters are only passed the first time on a new session after sign in. In subsequent calls, only the token will be available.
The default basePath is "/api/auth" for next-auth (NextAuth.js v4), and "/auth" for all other Auth.js frameworks.
The default updateAge is 24 * 60 * 60 seconds (24 hours). This throttles how frequently the session is written to the database to extend it.
Available events include: signIn (on successful sign in), signOut (on signout), createUser (user created), updateUser (user updated), linkAccount (account linked to user), session (session is active), and error (error in authentication flow).
The JWT maxAge defaults to session.maxAge, which is 60 * 60 * 24 * 30 seconds (30 days).
No, by default NextAuth.js does not include an adapter any longer. If you would like to persist user/account data, you must install one of the many available adapters.
You can customize: signIn (custom sign-in page), signOut (custom sign-out page), error (error page with error code in query string), verifyRequest (check email message page), and newUser (page for new users on first sign in).
The theme.colorScheme can be set to "light" (force light theme), "dark" (force dark theme), or "auto" (follow preferred system theme).
The default session strategy is "jwt" (an encrypted JWT stored in the session cookie) when no adapter is configured.
Yes, if you override the encode method, both encode and decode methods must be defined at the same time.
The secret is required in production. If not provided, it will be auto-generated based on a hash of all your provided options, but this is volatile and strongly not recommended for production.
Setting allowDangerousEmailAccountLinking: true enables automatic account linking for accounts with the same email address, even if they use different OAuth providers.
You can set NEXTAUTH_SECRET as an environment variable. If you set NEXTAUTH_SECRET, you don't have to define the secret option explicitly. AUTH_SECRET is also an alias for NEXTAUTH_SECRET.
No, the debug option is meant for development only and you should consider removing this option when deploying to production.
No, when logger options are set, the debug option is ignored. However, if the debug level is defined in the logger, it will be called regardless of the debug: false option.
The default maxAge for sessions is 30 * 24 * 60 * 60 seconds (30 days).
No, you do not need the NEXTAUTH_URL environment variable in Vercel. NextAuth automatically detects when you deploy to Vercel.
No, events are asynchronous functions that do not return a response. They are useful for audit logs, reporting, or handling other side-effects.
By default, only URLs on the same origin as the site are allowed. Relative callback URLs (starting with "/") are allowed and will be prefixed with baseUrl. If the URL has a different origin than baseUrl, it returns baseUrl instead.
Session Management > Session Configuration
33 questionsWhen an adapter is configured, the default session strategy automatically changes to "database" instead of "jwt".
AUTH_SECRET is the preferred naming going forward, though NEXTAUTH_SECRET is still supported as an alias for backward compatibility.
The default is true. Session cookies have httpOnly: true by default to prevent client-side JavaScript from accessing them.
No, session.updateAge is ignored if using JSON Web Tokens. It only applies when using the database session strategy.
When useSecureCookies is true, the session cookie is named __Secure-next-auth.session-token (or __Secure-authjs.session-token in newer versions).
The default session.maxAge is 30 days (2,592,000 seconds or 60 * 60 * 24 * 30).
When using the database strategy, the session cookie only contains a sessionToken value (not the full user data), which is used to look up the session in the database.
The default is 'lax'. This allows cookies to be sent with top-level navigations and GET requests initiated by third-party websites.
No, by default NextAuth.js does not set an explicit domain for session cookies. This results in a cookie whose domain is that of the current page (e.g., example.com), not including subdomains.
The session object must contain: user (with name, email, and image properties) and expires (a Date). The session callback must return this session object.
Yes, you can pass an array of secrets where the first secret that successfully decrypts the JWT will be used. This is useful for rotating secrets without invalidating existing sessions - add the newer secret to the start of the array.
NextAuth.js will throw a MissingSecretError with the message "Please define a secret in production." The secret is required for signing cookies and creating encryption keys.
You must specify the full route to the API endpoint, for example: NEXTAUTH_URL=https://example.com/custom-route/api/auth
Yes, you can explicitly set session.strategy: "jwt" to force JWT sessions even when an adapter is configured, though this can result in faster lookups at the cost of lowered security.
When useSecureCookies is false, the session cookie is named next-auth.session-token (without the __Secure- prefix).
No, generateSessionToken is specifically for database-based sessions only. With JWT sessions, no session token is generated because all session data is stored in the JWT itself.
You can run 'npx auth secret' which will autogenerate a random value and put it in your .env.local file. Alternatively, use 'openssl rand -base64 32' to generate a secure secret.
The default session.updateAge is 24 hours (86,400 seconds or 24 * 60 * 60). This setting throttles how frequently to write to the database to extend a session.
The default refetchOnWindowFocus is true, meaning the SessionProvider automatically refetches the session when the user switches between windows/tabs.
NextAuth.js uses the __Host- prefix for CSRF token cookies (not __Secure-) when secure cookies are enabled. This is stricter than __Secure- as it requires the cookie to be set from a secure origin with path=/.
The default is: randomUUID?.() ?? randomBytes(32).toString("hex"). It uses randomUUID() if available, otherwise falls back to generating 32 random bytes converted to a hexadecimal string (64 characters).
In v3 and v4, when encryption was enabled, the default algorithm was A256GCM with an auto-generated key.
Setting session.updateAge to 0 causes the session to be updated in the database every time it is accessed, disabling the throttling behavior.
The default is false for HTTP sites (e.g., http://localhost:3000) for developer convenience.
The default JWT encryption algorithm is A256CBC-HS512. The JWT is encrypted by default using this algorithm (JWE).
No, NEXTAUTH_URL is not required on Vercel if "Automatically expose System Environment Variables" is checked in Project Settings. NextAuth automatically detects the URL when deployed to Vercel.
The default is true for HTTPS sites. When true, all cookies set by NextAuth.js will only be accessible from HTTPS URLs.
The jwt.maxAge defaults to session.maxAge, which is 30 days (2,592,000 seconds).
NextAuth.js implements CSRF protection using the "double submit cookie method" with a signed HttpOnly, host-only cookie on all authentication routes.
The default refetchInterval is 0, meaning the session is not automatically polled/refetched on a timer.
The default session strategy is "jwt" - an encrypted JWT (JWE) stored in the session cookie.
The CSRF token must be passed as a form variable named csrfToken in all POST submissions to any NextAuth.js API endpoint.
Custom Pages > Error Page
33 questionsConfiguration and AccessDenied are the primary error codes passed to the error page. Configuration indicates a server configuration problem, and AccessDenied occurs when access is restricted through the signIn or redirect callback.
Server Components receive a searchParams prop automatically. You can access the error with searchParams.error.
Use useSearchParams() from 'next/navigation'. It's recommended to wrap the Client Component in a <Suspense/> boundary.
The default error page path is /api/auth/error. NextAuth automatically redirects errors to this path with the error code in the query string.
'OAuthCreateAccount' means NextAuth could not create an OAuth provider user in the database.
'Callback' indicates an error in the OAuth callback handler route.
OAuthSignin, OAuthCallback, OAuthCreateAccount, EmailCreateAccount, Callback, OAuthAccountNotLinked, EmailSignin, and CredentialsSignin are passed as error query parameters to the sign-in page.
'EmailCreateAccount' means NextAuth could not create an email provider user in the database.
'EmailSignin' indicates that sending the e-mail with the verification token failed.
'CredentialsSignin' indicates that the authorize callback returned null in the Credentials provider, meaning authentication failed.
'SessionRequired' means the content of the requested page requires you to be signed in at all times. The user will be redirected to the sign-in page.
Yes, you can define theme.brandColor to set a custom accent color and theme.logo with a URL to a logo that will be rendered above the primary card on the default pages.
'Verification' is related to the Email provider and indicates that the token has expired or has already been used.
When the signIn callback returns false, the user is redirected to the error page with error=AccessDenied in the query string.
The error code is passed in the query string as ?error=. For example, if the error is 'AccessDenied', the URL will be /auth/error?error=AccessDenied.
'OAuthAccountNotLinked' occurs when the email on the account is already linked, but not with this OAuth account. This prevents account linking for security reasons.
'AccessDenied' usually occurs when you restrict access through the signIn callback or redirect callback. It's always the error code when you return false from the signIn() callback.
The 'Configuration' error code indicates there is a problem with the server configuration. It suggests you should check if your NextAuth options are correct.
'OAuthCallback' indicates an error in handling the response from an OAuth provider during the callback phase.
A page file at pages/auth/error.js (or .tsx) should exist. The configuration path refers to the route, which corresponds to the physical file location.
Use useRouter from 'next/router': const router = useRouter(); const error = router.query.error;
The URL will contain error=CredentialsSignin&code=credentials, where the code parameter is configurable.
In the AuthConfig, use the pages option: pages: { error: '/auth/error' }. Auth.js forwards errors as error query parameters in the URL.
Returning null will display an error on the sign-in page advising the user to check their details, with error=CredentialsSignin in the URL.
The full error is always logged on the server (in the server terminal) for debugging purposes, even though the client receives restricted error codes.
The pages.error property accepts a string value representing the path to the custom error page (e.g., '/auth/error').
By default, a verification token is valid for 24 hours from when it's sent.
NextAuth purposefully restricts the returned error codes for increased security. This prevents exposing sensitive information to potential attackers. The error code is included in the URL, so it must not hint at sensitive errors.
NextAuth automatically creates simple, unbranded authentication pages for handling Sign in, Sign out, Email Verification, and displaying error messages.
'OAuthSignin' indicates an error in constructing an authorization URL for an OAuth provider.
NextAuth redirects to /api/auth/signin?error=SessionRequired&callbackUrl=<current-page-url> where callbackUrl is the URL the user was trying to access.
When you throw an Error from the authorize callback, the user will be sent to the error page with the error message as a query parameter.
The property name is error within the pages configuration object. Example: pages: { error: '/auth/error' }
Next.js App Router Integration > Middleware Protection
32 questionsgetToken() supports options including: secret (for token encryption), cookieName (to specify custom session token cookie name), secureCookie (boolean for secure prefixed cookie names), and raw (to get raw token without decrypting).
The withAuth function augments the Request with req.nextauth.token, which contains the user's decoded JWT token.
You can customize signIn, signOut, error, verifyRequest, and newUser pages. For example: pages: { signIn: "/login", error: "/error" }
Following the process to remove Edge Runtime errors in middleware results in no database access in the middleware, resulting in no ability to protect resources at the middleware level. This requires using JWT for storing the session and limits the middleware to just checking data stored in the JWT.
No, the middleware function doesn't call the JWT callback. Therefore, if the access token expires, in the authorized callback, the token is outdated and not refreshed.
You can use openssl rand -base64 32 or visit https://generate-secret.vercel.app/32 to generate a random value.
A common issue is that the middleware authorized callback receives a null value for the token property when NEXTAUTH_SECRET is not defined. Adding the NEXTAUTH_SECRET environment variable resolves this issue.
The authorized callback receives an object with { req, token } parameters, where req is the request object and token is the user's JWT token.
Create a middleware.js file at the root or in the src directory with: export { default } from "next-auth/middleware". This will require authentication for your entire site.
Export a config object with a matcher property. For example: export const config = { matcher: ["/dashboard"] }. Only the matched routes will require authentication.
The pages configuration should match the same configuration in [...nextauth].ts so that the next-auth Middleware is aware of your custom pages and won't end up redirecting to itself when an unauthenticated condition is met, which would cause an infinite redirect loop.
The authorized callback is invoked when a user needs authorization. It receives { request, auth } parameters and you can override default behavior by returning a NextResponse. For example: async authorized({ request, auth }) { return !!auth?.user }
Move callbacks to auth.config.ts instead of auth.ts. When callbacks are in auth.config.ts, the execution order is jwt → session → middleware, ensuring the session is already populated before middleware runs. When in auth.ts, the order is middleware → jwt → session.
Create an auth.config.ts file at the project root containing all common configuration that does not rely on the adapter. This allows lazy initialization for middleware without edge-incompatible adapters.
AUTH_SECRET is the required environment variable in Auth.js v5 (NextAuth v5). If not set in production, NextAuth.js will throw an error.
The withAuth middleware only supports "jwt" as the session strategy. It does not support database sessions because databases at the Edge need to mature enough to ensure a fast experience.
You access the session via req.auth. For example: const isAuthenticated = !!req.auth. The auth function wraps your middleware and augments the request with the auth property.
The middleware.ts file should be placed at src/middleware.ts. If the middleware file is written outside the src folder, the middleware will not work.
Next.js Middleware code always runs in an edge runtime where database functionality (such as TCP sockets for PostgreSQL) is not available. Additionally, only the jwt session strategy is supported because databases at the Edge need to mature.
Use split configuration: (1) Create auth.config.ts with edge-compatible configuration without the adapter, (2) Create a full auth file that imports the config and adds the database adapter with JWT session strategy, (3) Import only the config in middleware without the database adapter.
You must pass the basePath prop to SessionProvider. For example:
You could end up in an infinite redirect loop. Make sure that the page you are redirecting to is not protected by the authorized callback.
Whenever auth() is passed the res object (response object), it will rotate the session expiry.
You must also pass the same decode method to withAuth in order to read the custom-signed JWT correctly. Both the NextAuth configuration and middleware need the same decode method.
The default behavior is to redirect the user to the sign-in page.
The middleware function will only be invoked if the authorized callback returns true.
NEXTAUTH_SECRET is required in production. Not providing NEXTAUTH_SECRET will throw an error in production. This random string is used to hash tokens, sign/encrypt cookies and generate cryptographic keys.
Use matcher: ["/dashboard/:path*"] to protect all routes inside the dashboard folder, including nested routes like /dashboard/settings and /dashboard/profile.
NextAuth.js version 4.2.0 is the minimum version required. With NextAuth.js 4.2.0 and Next.js 12, you can protect your pages via the middleware pattern.
You must pass the same cookieName to getToken(). For example: getToken({ req, cookieName: "custom-session-token" }). The same custom cookie name must be provided to both getToken and the withAuth middleware.
The recommended pattern is: matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"] which excludes /api routes, static files, image optimizations, and favicon.
Database Adapters > Adapter Models
32 questionsThe basic user management methods are: createUser(user), getUser(id), getUserByEmail(email), getUserByAccount({ providerAccountId, provider }), updateUser(user), and deleteUser(userId).
The deleteUser and unlinkAccount methods are planned to be required in a future release but are not yet invoked by NextAuth.
The Session model uses onDelete: Cascade for its relationship with the User model. When a User is deleted, all associated Session records are automatically deleted.
In NextAuth v4, provider_id/providerId was renamed to provider on the Account model, and the compound_id/compoundId field was removed entirely.
The three required fields are: identifier (String, typically the user's email), token (String, unique), and expires (DateTime, expiration timestamp).
Required methods are: linkAccount, createSession, getSessionAndUser, updateSession, deleteSession, and updateUser.
The five standard fields are: id (String, required), name (String?, optional), email (String?, optional), emailVerified (DateTime?, optional), and image (String?, optional).
When using database session strategy, the session cookie only contains a sessionToken value, which is used to look up the session in the database.
The VerificationToken model has both a unique constraint on the token field (@unique) and a compound unique constraint @@unique([identifier, token]) on the combination of identifier and token.
The created_at/createdAt and updated_at/updatedAt fields were removed from all Models (including User, Account, Session, and VerificationToken) in NextAuth v4.
For oauth/oidc providers, the providerAccountId contains the OAuth account's id, returned from the profile() callback.
For credentials providers, the providerAccountId contains the id returned from the authorize() callback.
The createVerificationToken method receives a VerificationToken object with identifier, expires, and token properties. The token is already hashed when passed to this method.
The Account model uses onDelete: Cascade for its relationship with the User model. This means when a User is deleted, all associated Account records are automatically deleted.
The expires_at field stores an integer (Int?) representing the absolute Unix timestamp in seconds when the access_token expires. It is NOT a DateTime object.
Only the id field is required in the User model as it serves as the primary key. All other standard NextAuth User fields (name, email, emailVerified, image) are optional and can be null.
No, as of NextAuth v4, the library no longer ships with an adapter included by default. You need to install one of the available adapters separately.
The Account model has a unique compound constraint on the combination of provider and providerAccountId, defined as @@unique([provider, providerAccountId]). This ensures that each provider account can only be linked once.
The sessionToken field has a unique constraint defined as sessionToken String @unique. This ensures each session token is unique across all sessions.
For email providers, the providerAccountId contains the user's email address.
The four core models are: User (for information like name and email address), Account (for OAuth account information associated with a User), Session (for database sessions), and VerificationToken (for passwordless email authentication).
The three required fields are: type (the Provider's type), provider (String identifying the authentication provider), and providerAccountId (String value identifying the specific account with that provider).
For email provider support, the adapter must implement: createVerificationToken and useVerificationToken.
Yes, current Prisma adapter schemas include an id field as the primary key in the VerificationToken model, along with identifier, token, expires, and the unique constraints.
The OAuth token fields are: refresh_token (String?), access_token (String?), expires_at (Int?), token_type (String?), scope (String?), id_token (String?), and session_state (String?). All are optional nullable fields.
The current NextAuth Prisma adapter uses String @id @default(cuid()) for the Session and User model ID fields. CUID is the default strategy.
When adding new fields to the User, Account, Session, or VerificationToken models, you must provide default values for new fields since the adapter is not aware of them and automatically creates fields when new users sign up.
A single User can have multiple Accounts (one-to-many), but each Account can only have one User (many-to-one).
The useVerificationToken method retrieves and deletes the used-up token from the database. It has the responsibility to delete the token after verification.
The four required fields are: id (String, primary key), sessionToken (String, unique), userId (String, foreign key to User), and expires (DateTime).
Yes, the email field is optional but if one is specified for a User then it must be unique. In Prisma schemas it's defined as email String? @unique.
The valid AdapterAccountType values are: "oauth", "oidc", "email", and "webauthn". Note that "credentials" is not included since credentials-based authentication typically doesn't create persisted account records.
Security > Production Checklist
30 questionsThe Credentials provider can only be used if JSON Web Tokens are enabled for sessions because users authenticated this way are not persisted in the database.
No, if you are using the Essential Next.js Build Plugin within your project, you do not need to set the NEXTAUTH_URL environment variable as it is set automatically as part of the build process. However, you will want to make sure you add your NEXTAUTH_SECRET environment variable in the project settings.
The functionality provided for credentials based authentication is intentionally limited to discourage use of passwords due to the inherent security risks associated with them and the additional complexity associated with supporting usernames and passwords.
NEXTAUTH_SECRET (or AUTH_SECRET in newer versions) is required in production. Not providing any secret or NEXTAUTH_SECRET will throw an error in production.
NextAuth.js v5 can automatically infer environment variables that are prefixed with AUTH_, such as AUTH_GITHUB_ID and AUTH_GITHUB_SECRET which will be used as the clientId and clientSecret options for the GitHub provider.
Set debug: process.env.NODE_ENV !== "production", so you can commit this without needing to change the value.
No, you do not need the NEXTAUTH_URL environment variable in Vercel. Using System Environment Variables, NextAuth.js automatically detects when you deploy to Vercel so you don't have to define this variable.
__Secure- for most cookies, and __Host- prefix for the CSRF token when useSecureCookies is enabled.
Enabling the debug option in production can lead to sensitive information being saved in your logs.
database - If you use an adapter, NextAuth.js defaults to 'database' strategy instead of JWT.
No, the debug option is meant for development only and you should consider removing this option when deploying to production.
Overriding the default cookie names and options is an advanced option and using it is not recommended as you may break authentication or introduce security flaws into your application.
A random string used to hash tokens, sign/encrypt cookies and generate cryptographic keys. It's used to encrypt the NextAuth.js JWT, and to hash email verification tokens.
When deploying your application behind a reverse proxy, you'll need to set AUTH_TRUST_HOST equal to true, which tells Auth.js to trust the X-Forwarded-Host header from the reverse proxy.
Changing NEXTAUTH_SECRET in production invalidates all existing sessions, logging out every user.
30 days (30 * 24 * 60 * 60 seconds). This applies to both JWT sessions and database-persisted sessions.
jwt - The default session strategy is JWT (an encrypted JWT stored in the session cookie).
Make sure 'Automatically expose System Environment Variables' is checked in your Project Settings.
If you want to log debug messages during production, NextAuth.js recommends setting the logger option with proper sanitization of potentially sensitive user information.
true - The default for all site URLs that start with https:// is true, meaning all cookies will only be accessible from HTTPS URLs.
Setting this option to false in production is a security risk and may allow sessions to be hijacked.
No, the synchronize option should not be enabled against production databases as it may result in data loss if there is a difference between the schema found in the database and the schema that the version of NextAuth.js being used is expecting.
[origin]/api/auth/callback/[provider] - For example: https://company.com/api/auth/callback/google
AUTH_SECRET - This is an alias for NEXTAUTH_SECRET and is the preferred naming going forward.
npx auth secret - This command autogenerates a random value and puts it in your .env.local file.
AUTH_SECRET should be a cryptographically secure random string of at least 32 characters.
Auth.js automatically infers trustHost to be true when it detects environment variables indicating the application is running on VERCEL or CF_PAGES (Cloudflare Pages).
OAuth Providers > Microsoft/Azure AD
30 questionsNo, access_token and refresh_token are not persisted by default and must be manually saved in the jwt callback.
The offline_access scope is used to request a Refresh Token but is never returned as a scope because it cannot be part of the access token itself.
Valid values include: "login" (forces user to enter credentials), "consent" (triggers the consent dialog), and "select_account" (shows account picker).
The default size is 48x48 pixels to avoid running out of space when the session is saved as a JWT.
Azure AD is designed for internal authentication within organizations, while Azure AD B2C is meant for authenticating external users (consumers) outside of your company with broader authentication method support.
Valid sizes are: 48, 64, 96, 120, 240, 360, 432, 504, or 648 pixels.
The default value is "common", which allows both Microsoft accounts and school/work accounts.
authorization_code (standard OAuth 2.0 authorization code grant type)
The format is {APP_BASE_URL}/api/auth/callback/azure-ad. For example: https://yourapplication.com/api/auth/callback/azure-ad for production or http://localhost:3000/api/auth/callback/azure-ad for development.
Azure AD returns the profile picture in an ArrayBuffer instead of a URL. NextAuth converts it to a base64 encoded image string.
"openid profile user.Read email" or "openid profile email offline_access" depending on whether refresh tokens are needed.
Single tenant: https://login.microsoftonline.com/{tenant-id}/v2.0, Multi-tenant: https://login.microsoftonline.com/organizations/v2.0, Multi-tenant + Personal: https://login.microsoftonline.com/common/v2.0
import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id" (Azure AD has been renamed to Microsoft Entra ID)
The three required parameters are: clientId (Application/client ID from Azure AD app registration), clientSecret (Client Credentials value generated in Azure AD), and tenantId (Directory/tenant ID from Azure AD).
Valid values are: "common" (multi-tenant + personal accounts), "organizations" (multi-tenant work/school accounts only), "consumers" (personal Microsoft accounts only), or a specific Directory (tenant) ID for single-tenant.
{YOUR_BASE_URL}/api/auth/callback/microsoft-entra-id. For example: http://localhost:3000/api/auth/callback/microsoft-entra-id for development.
The "offline_access" scope must be included in the authorization params.
WebAuthn and Passkeys
30 questionsTo register a new passkey: signIn("passkey", { action: "register" }). To authenticate: signIn("passkey").
The counter field (INTEGER) tracks the number of times the authenticator has been used.
If you're using next-auth with Next.js and middleware, you should ensure that your database client is "edge compatible". If using an older version of Prisma or another adapter that is not edge compatible, you'll need to make adjustments.
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 it.
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), and transports (TEXT, nullable).
The transports field stores concatenated transport flags as TEXT. It can contain values like BLE, Hybrid, Internal, NFC, or USB.
The userId field has a foreign key constraint that references the User table's id field with CASCADE delete and update rules.
The default basePath for NextAuth is /api/auth, meaning all authentication requests will be routed to this path by default.
It is marked as optional in the peerDependenciesMeta, meaning it's only needed for custom WebAuthn signin pages.
@simplewebauthn/browser provides client-side WebAuthn functionality and is required when using custom signin pages with the WebAuthn signIn function.
The WebAuthn provider requires changes to all framework integrations as well as any database adapter that plans to support it, and is currently only supported in specific framework integrations and database adapters.
As of @prisma/[email protected], Prisma began making changes to error out at query-time rather than instantiation, allowing usage with next-auth in Edge runtimes as long as no queries are executed on the edge.
The credentialID is stored as a Base64 encoded credential ID in a TEXT field with a unique constraint.
You must set experimental: { enableWebAuthn: true } in your Auth.js configuration.
The Authenticator table uses a composite primary key consisting of (userId, credentialID).
The Passkeys provider requires an additional table called Authenticator in the database.
NextAuth specifies @simplewebauthn/browser version ^9.0.1 as an optional peer dependency.
Prisma version 5.12.0 or above is recommended for edge runtime compatibility. Prisma shipped edge runtime support for their client in version 5.12.0.
The credentialBackedUp field (BOOLEAN) indicates whether the client authenticator backed up the credential.
The credentialPublicKey is stored as a Base64 encoded string in a TEXT field.
Yes, there is a unique index on the credentialID field to ensure credential IDs are globally unique.
If you did not setup a custom sign in page, the user will be redirected to /[basePath]/signin (e.g., /api/auth/signin with default basePath).
Passkeys are supported in multiple adapters including Prisma and Drizzle, with their respective documentation pages providing detailed migration steps.
Navigating to your /signin route should include a "Signin with Passkeys" button when using the built-in Auth.js pages.
The WebAuthn / Passkeys provider is experimental and not yet recommended for production use.
The RelayingParty type has three properties: id (string - the website's domain name), name (string - the website's name), and origin (string - the website's origin).
updateAuthenticatorCounter?(credentialID: AdapterAuthenticator["credentialID"], newCounter: AdapterAuthenticator["counter"]): Awaitable
The enableWebAuthn experimental flag means features are not guaranteed to be stable and may change or be removed without notice.
OAuth Providers > Google Provider
30 questionsYes, Google will reject the OAuth request if the redirect URI isn't an exact match. Even small differences like trailing slashes or query params will cause failure.
"openid email profile" (which maps to https://www.googleapis.com/auth/userinfo.profile and https://www.googleapis.com/auth/userinfo.email)
profile.sub (the unique identifier from Google's OAuth provider)
true - this means user information is extracted from the id_token claims instead of making a request to the userinfo endpoint
When set to true, it enables automatic account linking based on email addresses. This is considered safe for Google since Google verifies email addresses.
The user needs to remove the application from their account at https://myaccount.google.com/permissions and sign in again.
Yes, environment variables in .env are now automatically inferred in v5, so you no longer need to manually set them in auth.ts
import Google from "next-auth/providers/google" (note: the word "Provider" is no longer required in v5)
["pkce", "state"] - both PKCE and state checks are enabled by default for security
In the Google Cloud Console under 'API & Services' > 'Credentials' > 'OAuth 2.0 Client IDs' in the 'Authorized redirect URIs' section
No. Google only provides a Refresh Token to an application the first time a user signs in.
OAuthAccountNotLinked error - this can be resolved by setting allowDangerousEmailAccountLinking: true
email_verified - a boolean property that can be used to restrict access to people with verified accounts
Set authorization params to: { prompt: "consent", access_type: "offline", response_type: "code" }. However, this will ask all users to confirm access every time they sign in.
In a .env.local file in the project root as GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET (and ensure .env.local is in .gitignore)
{ id: profile.sub, name: profile.name, email: profile.email, image: profile.picture }
clientId and clientSecret, typically provided via environment variables: process.env.GOOGLE_CLIENT_ID and process.env.GOOGLE_CLIENT_SECRET
OAuth Providers > Discord Provider
30 questionsNextAuth v5 (Auth.js) uses AUTH_DISCORD_ID and AUTH_DISCORD_SECRET. The general format is AUTH_
The 'connections' scope provides access to the user's third-party connections (such as linked Steam, Twitch, or other accounts).
No, Discord is primarily an OAuth2 provider and does not provide a standard OIDC .well-known/openid-configuration endpoint.
In NextAuth v5, you export { handlers, auth, signIn, signOut } from the NextAuth() function in your auth.ts file.
In NextAuth v5 (Auth.js), the configuration should be in an auth.ts (or auth.js) file in the root of the repository, not in an API route like v4.
The 'guilds.join' scope allows the application to add users to a guild/server. It requires the client to have a bot token, and the bot must have CREATE_INSTANT_INVITE permission in the target guild.
The default scopes are 'identify' and 'email'. These are specified in the authorization URL as 'https://discord.com/api/oauth2/authorize?scope=identify+email'.
Use the jwt and session callbacks: In jwt callback, set token.id = account.providerAccountId when account exists. In session callback, set session.user.id = token.id to make it available in the session.
Override the authorization property with custom scopes: DiscordProvider({ clientId: process.env.DISCORD_CLIENT_ID, clientSecret: process.env.DISCORD_CLIENT_SECRET, authorization: { params: { scope: 'identify email guilds' } } })
The 'identify' scope provides access to basic user information including id, username, avatar, and discriminator, but does not include the user's email address.
Valid values are 'consent' (requests user to reapprove authorization even if previously authorized) and 'none' (skips authorization screen and redirects without requesting authorization). The default is 'consent'.
The 'email' scope provides access to the user's email address and verified status.
The Discord provider returns: id, username, avatar, discriminator, public_flags, flags, banner, accent_color, global_name, avatar_decoration_data, banner_color, clan, locale, email (if email scope is granted), verified, and mfa_enabled.
The format is https://cdn.discordapp.com/avatars/{USER_ID}/{AVATAR_HASH}.{extension} where extension is 'png' for static avatars or 'gif' for animated avatars.
Discord removed discriminators from the account creation process in September 2023 and forcefully migrated remaining accounts in March 2024. Migrated users now have discriminator values of '0000' or 0, and the new system uses unique alphanumeric usernames with non-unique display names.
Discord expects the client_secret in the request body of the token request, using the client authentication method 'Request body (client_secret_post)'.
The token endpoint requires Content-Type: application/x-www-form-urlencoded. JSON content is not permitted and will return an error.
For Next.js applications, the callback URL format is:
NextAuth v4 uses DISCORD_CLIENT_ID and DISCORD_CLIENT_SECRET. These are accessed via process.env.DISCORD_CLIENT_ID and process.env.DISCORD_CLIENT_SECRET.
For users without custom avatars, the URL is https://cdn.discordapp.com/embed/avatars/{number}.png where {number} is calculated based on the user's discriminator or user ID.
The provider checks if profile.avatar starts with 'a_' to determine the format (gif or png), then constructs the URL as https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}. If profile.avatar is null, it uses a default avatar URL.
For SvelteKit and Qwik, the callback URL format is https://example.com/auth/callback/discord (without the /api prefix used in Next.js).
In the old system, the default avatar number was calculated using discriminator % 5, which gives a number from 0-4 corresponding to one of five default avatar variations.
The 'guilds.members.read' scope allows access to member information (nickname, avatar, roles, etc.) in servers the user belongs to.
The Discord user ID is stored in the providerAccountId field of the accounts table. You can query this field to retrieve the Discord-specific user ID.
The 'guilds' scope provides access to the list of guilds (servers) that the user is a member of.
Check if the avatar hash starts with 'a_'. If profile.avatar.startsWith('a_'), the avatar is animated and should use the .gif extension instead of .png.
Database Adapters > Prisma Adapter
30 questions@default(cuid()) - All models (User, Account, Session, VerificationToken) use CUID by default, though UUID can be used as an alternative.
The current recommendation uses @@unique([provider, providerAccountId]) with a separate id field as the primary key, not @@id([provider, providerAccountId]).
npm install @prisma/client @auth/prisma-adapter followed by npm install prisma --save-dev
Yes, you can use @map for column names and @@map for table names (e.g., @@map('accounts')). However, Prisma model names must remain lowercase (user, account, session, verificationToken) as they are hardcoded in the adapter.
Int? (optional Integer) - stores a Unix timestamp in seconds representing the absolute timestamp when the access_token expires.
No, the Authenticator model is optional and only needed for WebAuthn (passwordless authentication) support.
No, the Credentials provider requires JWT session strategy. You must set session: { strategy: 'jwt' } when using Credentials provider with Prisma adapter.
Only id (String @id @default(cuid())) and email (String? @unique) are in the base schema. The email field is nullable despite the @unique constraint.
The email field is nullable (String?) because some OAuth providers like Facebook may not provide email addresses.
userId (String), type (String), provider (String), and providerAccountId (String) are required.
@@unique([provider, providerAccountId]) - ensures each OAuth/OIDC account is unique per provider.
The default session strategy changes from 'jwt' to 'database' when an adapter is configured, though you can explicitly specify 'jwt' to override this.
Import as a named export: import { PrismaAdapter } from "@auth/prisma-adapter" then use it as: adapter: PrismaAdapter(prisma) where prisma is your PrismaClient instance.
credentialID (String @unique), userId (String), providerAccountId (String), credentialPublicKey (String), counter (Int), credentialDeviceType (String), credentialBackedUp (Boolean), and transports (String?) with @@id([userId, credentialID]).
DateTime? (optional/nullable DateTime) - can be null or a timestamp indicating when the email was verified.
Yes, the sessionToken field has a @unique constraint, which creates a unique index allowing NextAuth to efficiently look up sessions.
The type field uses AdapterAccountType which includes 'oauth', 'oidc', 'email', and 'webauthn'.
Yes, since @prisma/[email protected], but you must use JWT session strategy (not database sessions) if performing session checks in edge environments like middleware.
30 days (30 * 24 * 60 * 60 seconds) is the commonly documented default.
onDelete: Cascade - when a User is deleted, all associated Account records are automatically deleted.
PostgreSQL, MySQL, and SQLite. MongoDB is NOT supported by the Prisma adapter (use TypeORM adapter for MongoDB).
id (String), sessionToken (String @unique), userId (String), and expires (DateTime) are required.
Environment variables should be prefixed with AUTH_ in v5; NEXTAUTH_ is no longer used.
Fields like refresh_token, access_token, and id_token use @db.Text because OAuth tokens can exceed the default VARCHAR(191) limit in MySQL.
No, only @@unique([identifier, token]) is needed in current documentation. The individual @unique on token field is redundant.
The counter (Int) is a signature counter from the WebAuthn specification that increments with each authentication and helps detect cloned authenticators.
identifier (String), token (String), and expires (DateTime) with a @@unique([identifier, token]) composite constraint.
@auth/prisma-adapter (for Auth.js v5). The older package @next-auth/prisma-adapter was used for NextAuth.js v4 and is now considered legacy.
OAuth Providers > Custom OAuth Provider
30 questionsThe default scope for Google provider is "openid email profile".
The callback URL follows the format: [origin]/api/auth/callback/[provider], where [provider] is the id of your provider. For example: http://localhost:3000/api/auth/callback/twitter
The default grant_type is "authorization_code". This is the standard OAuth 2.0 authorization code flow and can be explicitly set via params: { grant_type: "authorization_code" }.
The profile callback receives two parameters: profile (raw user information from the provider) and tokens (containing access_token, id_token, and/or refresh_token depending on the provider).
Setting allowDangerousEmailAccountLinking: true enables automatic account linking when an account with the same email already exists from a different provider. This is disabled by default for security but is safe for trusted providers like Google, GitHub, or Microsoft that verify emails.
For OAuth 2.0 providers, you need: id, name, type (set to "oauth"), authorization (URL), token (URL), userinfo (URL), clientId, clientSecret, and a profile callback function to map user data.
The wellKnown option points to an OIDC discovery endpoint (e.g., /.well-known/openid-configuration) that automatically configures authorization, token, and userinfo endpoints without manual configuration.
The three main endpoints are: authorization (replaces authorizationUrl/authorizationParams/scope), token (replaces accessTokenUrl/headers/params), and userinfo (replaces profileUrl).
The id is a unique identifier used in signIn("provider-id") calls and forms part of the callback URL path (/api/auth/callback/[id]).
The code_verifier is saved in a cookie called __Secure-next-auth.pkce.code_verifier which expires after 15 minutes (900 seconds).
The name field is optional text used on the default NextAuth login page as the button text (e.g., "Sign in with [name]").
You can set token as a URL string "https://example.com/oauth/token", an object with url property, or use a custom async request function: token: { url: "...", async request(context) { ... } }.
When idToken: true is set, NextAuth decodes the id_token JWT to extract user information instead of making an additional request to the userinfo endpoint. This is more efficient for OIDC providers.
The OpenID Connect specification recommends using HTTP GET for the UserInfo Request with the Access Token sent via the Authorization header field.
NextAuth uses openid-client, a certified OpenID Connect library, under the hood to handle OAuth 2.0 and OpenID Connect protocol conformance.
Use the client option with token_endpoint_auth_method property, for example: client: { token_endpoint_auth_method: "none" } or client: { token_endpoint_auth_method: "client_secret_post" }.
The profile callback must return an object with at minimum: id (user identifier), and optionally name, email, and image fields.
Environment variables follow the pattern AUTH_[PROVIDER]ID and AUTH[PROVIDER]_SECRET. For example: AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET. NextAuth v5 automatically infers these variables.
Set checks: ["pkce", "state"] and add client: { token_endpoint_auth_method: "none" } to your provider configuration.
The default response_type is "code" for OAuth 2.0 providers, used in the authorization code flow.
Override only the specific parameter: authorization: { params: { scope: "your custom scope" } }. User-provided options are deeply merged with defaults, so you only need to override what needs to be different.
The checks option is an array specifying security checks to enable, such as ["pkce", "state", "nonce"]. PKCE provides proof key for code exchange, state provides CSRF protection, and nonce helps with OIDC security.
The default code_challenge_method is "S256". This cannot be configured to "plain" as S256 is Mandatory To Implement (MTI) per RFC7636 and is more secure.
Use the jwt callback to store it when account exists (first sign-in): if (account) { token.accessToken = account.access_token }, then expose it in the session callback: session.accessToken = token.accessToken.
Built-in OAuth providers are available at https://next-auth.js.org/providers/ and include popular services like Google, GitHub, Facebook, Twitter, and many others.
type: "oauth" requires an additional request to the userinfo endpoint to get user data. type: "oidc" extracts user information from the id_token claims, which is more efficient for OpenID Connect compliant providers.
For OIDC providers, you need: id (unique identifier), name (display name), type (set to "oidc"), issuer (URL to infer .well-known/openid-configuration), clientId, and clientSecret.
NextAuth.js supports OAuth 1.0, 1.0A, 2.0, and OpenID Connect (OIDC).
You can set authorization as a full URL string like "https://example.com/oauth/authorization?scope=email", or use an object with params: authorization: { params: { scope: "email", prompt: "consent" } }.
For the Google provider, NextAuth sets checks: ["pkce", "state"] by default.
OAuth Authentication > OAuth Flow
30 questionsThe checks parameter can include "pkce", "state", and "nonce". For example: checks: ["pkce", "state"]. These parameters are stored in httpOnly cookies for security during the OAuth flow.
By specifying idToken: true, the userinfo endpoint which is automatically discovered by the wellKnown endpoint is no longer used. The workaround is to force the userinfo call by specifying the request function in the userinfo option explicitly.
By default only URLs on the same URL as the site are allowed. The default implementation allows relative callback URLs (starting with '/'), allows absolute URLs on the same origin as the base URL, and defaults to the base URL for all other cases, rejecting external URLs.
You can override authorization parameters by configuring authorization.params.scope. For example: GoogleProvider({ authorization: { params: { scope: "openid email profile custom_scope" } } }). Parameters are deeply merged with defaults.
For OIDC compliant providers, use the wellKnown option with the .well-known/openid-configuration URL. The issuer URL is used to infer the .well-known/openid-configuration URL automatically. You can combine this with idToken: true to skip the userinfo endpoint call.
The signIn callback can return true to allow sign in, return false to deny access (displays a default error message), or return a string URL to redirect to (e.g., '/unauthorized').
Versions before v4.20.1 were affected by a vulnerability where attackers could intercept the authorization URL and strip away OAuth checks (nonce, state, pkce). This was addressed in v4.20.1 and upgrading is recommended.
NextAuth.js uses the 'double submit cookie method' with a signed HttpOnly, host-only cookie. For OAuth 2.0 providers that support checks: ['state'], the state parameter is checked against a hash of the CSRF token which must match for both POST and GET calls during sign-in.
The callbackUrl defaults to the page URL the sign-in is initiated from. You can specify a different callbackUrl as the second argument: signIn('google', { callbackUrl: '/dashboard' }).
Configure the Google provider authorization parameters with: prompt: 'consent', access_type: 'offline', and response_type: 'code'. For example: GoogleProvider({ authorization: { params: { prompt: 'consent', access_type: 'offline', response_type: 'code' } } })
Setting idToken: true at the top-level of your provider configuration allows NextAuth to decode the id_token to get user information instead of making an additional request to the userinfo endpoint. This is typically used with OpenID Connect (OIDC) compliant providers.
NextAuth.js does not handle concurrent refresh token requests properly when using single-use refresh tokens. If multiple requests occur simultaneously while the access token is expired, multiple refresh attempts may be made, causing race conditions.
Set client: { token_endpoint_auth_method: "none" } in your provider configuration along with checks: ["pkce", "state"]. This is required for public clients that should not send a client_secret when using PKCE.
The third argument is authorizationParams which allows passing additional authorization parameters at runtime. The signIn() function accepts three parameters: provider, options (including callbackUrl), and authorizationParams. Parameters must be encodable to the URL.
The profile callback returns id, name, email and image (or picture in older versions) by default. The only truly required field is 'id' to be able to identify the account when added to a database. If any fields are missing values, they should be set to null.
No, NextAuth.js doesn't automatically handle access token rotation for OAuth providers. This functionality must be implemented manually using the jwt callback and session callback to check token expiration and refresh when needed.
The redirect URI format is: [origin]/api/auth/callback/[provider], where [provider] is the id of your provider. For example: http://localhost:3000/api/auth/callback/twitter or https://next-auth-example.vercel.app/api/auth/callback/google.
Setting allowDangerousEmailAccountLinking: true enables automatic account linking when signing in with an OAuth provider if another account with the same email already exists. It's called 'dangerous' because not all OAuth providers verify email addresses, but it's safe to use with trusted providers like Google, GitHub, or Microsoft.
The arguments user, account, profile and isNewUser are only passed the first time the callback is called on a new session, after the user signs in. In subsequent calls, only the token parameter will be available.
The default scopes typically include 'openid email profile'. For OIDC providers, openid is required, and email and profile provide additional user information. Some providers may also include offline_access for refresh token support.
The default session strategy is 'jwt' (an encrypted JWT/JWE stored in the session cookie). However, if you configure an adapter, it automatically defaults to 'database' strategy instead.
NextAuth.js supports type: 'oauth' for OAuth 2.0 providers and type: 'oidc' for OpenID Connect providers. OIDC providers can use the wellKnown option to automatically discover endpoints from the .well-known/openid-configuration URL.
The account object contains: provider, type, providerAccountId, access_token (or accessToken), id_token, scope, expires_at, token_type, and refresh_token (when available). For OAuth 1.0 providers like Twitter, it contains oauth_token and oauth_token_secret instead of access_token and refresh_token.
The session callback is called whenever a session is checked, including calls to getSession(), useSession(), and /api/auth/session, any time session data is accessed on the client or server side.
The code_verifier is saved in a cookie called __Secure-next-auth.pkce.code_verifier which expires after 15 minutes. The maxAge property of state and pkce cookies is set to 900 seconds (15 minutes).
NextAuth.js supports OAuth 1.0, 1.0A, 2.0 and OpenID Connect. However, in version 5, OAuth 1.0 support has been deprecated and the library now focuses on OAuth 2.0 and OpenID Connect.
You can configure userinfo with either a simple URL string or an advanced configuration with a custom async request function. The request function receives a context object with access to tokens: userinfo: { url: 'https://example.com/oauth/userinfo', async request(context) { /* custom logic with context.tokens.access_token */ } }
The default code_challenge_method in NextAuth.js is "S256" (SHA-256 hashing). This is currently not configurable to "plain" as it is not recommended and only supported for backward compatibility in most cases.
When using JWT sessions (default), the session callback receives { session, token, user }. When using database sessions, it receives { session, user } where the User object is passed from the database instead of the token.
OAuth providers require both clientId and clientSecret to be configured. In NextAuth.js v5, these can be automatically inferred from environment variables prefixed with AUTH_, using the format AUTH_{PROVIDER}ID and AUTH{PROVIDER}_SECRET.
Session Management > JWT Sessions
30 questionsThe default maxAge is 30 days (2592000 seconds or 30 * 24 * 60 * 60). Both session.maxAge and jwt.maxAge default to this value.
The decode function receives an object with two parameters: { token, secret }. The token is the string to decode, and secret is used for decryption. It should return a JWT object or null if decoding failed.
The secureCookie option is a boolean that allows you to use secure prefixed cookie names. By default, the helper function automatically determines if it should use the secure prefixed cookie (true in production with HTTPS, false in development). This option is ignored if cookieName is explicitly specified.
The default path is '/' (root path), making the cookie accessible across the entire application domain.
If you use an adapter, NextAuth.js defaults the session strategy to 'database' instead of 'jwt'. You can still force JWT sessions by explicitly setting session: { strategy: 'jwt' }.
No, NextAuth.js middleware requires JWT sessions. If you want to use database sessions, you cannot use the middleware and have to perform authentication in getServerSideProps instead. This is because most databases/ORMs don't support non-Node.js/Edge runtimes where middleware executes.
The default cookie name is '__Secure-next-auth.session-token' in production (HTTPS) or 'next-auth.session-token' in development (HTTP).
JWT is the default session strategy. JSON Web Tokens are enabled by default if you have not specified an adapter, and are encrypted (JWE) by default.
In Auth.js v5, the salt used in the HKDF key derivation process is the session cookie name (e.g., 'authjs.session-token' or '__Secure-authjs.session-token'). This is different from v4, where the salt was an empty string.
Yes, NEXTAUTH_SECRET (or AUTH_SECRET) is required in production. Not providing any secret or NEXTAUTH_SECRET will throw an error in production. The secret is used to encrypt the NextAuth.js JWT and to hash email verification tokens.
Yes, by default NextAuth.js automatically revalidates sessions when windows gain focus (refetchOnWindowFocus is true by default). Session state is automatically synchronized across all open tabs/windows.
The session cookie name is used as salt. In v5, it's either 'authjs.session-token' (for HTTP/development) or '__Secure-authjs.session-token' (for HTTPS/production). The exact cookie name is retrieved from options.cookies.sessionToken.name.
Browsers typically have a cookie size limit of around 4096 bytes (4kb) per cookie. If you try to set a value that exceeds this limit, the cookie won't be created.
You can pass an array of secrets instead of a single string. The first secret in the array will be used for encrypting new JWTs, while all secrets in the array will be attempted when decrypting existing JWTs. The newer secret should be added to the start of the array.
Since v4, all JWT tokens are encrypted by default with A256GCM. Auth.js JWTs are encrypted using the A256CBC-HS512 algorithm by default.
You can use 'openssl rand -base64 32' or visit https://generate-secret.vercel.app/32 to generate a random value for NEXTAUTH_SECRET.
NextAuth.js sessions are rolling by default, meaning if the user is interacting with the site, the session won't expire. The expires value is rotated whenever the session is retrieved from the REST API, updating the expiry to avoid session timeout during active use.
NextAuth.js supports A256CBC-HS512 (default, requires 64-byte derived key) and A256GCM (requires 32-byte derived key). The default changed to A256CBC-HS512 in recent versions.
Auth.js v5 uses HKDF (HMAC-based Key Derivation Function) with SHA-256, using the cookie name as salt and the info string 'Auth.js Generated Encryption Key (${salt})', with a key length of 64 bytes for A256CBC-HS512 or 32 bytes for A256GCM.
Yes, encryption (JWE) adds additional overhead and reduces the amount of space available to store data in the JWT compared to only signing (JWS). This can be a concern when approaching the 4kb cookie size limit.
Both NEXTAUTH_SECRET and AUTH_SECRET are accepted. AUTH_SECRET and AUTH_URL are aliased for NEXTAUTH_SECRET and NEXTAUTH_URL for consistency in the newer Auth.js naming convention.
The jwt.maxAge value defaults to session.maxAge if not explicitly set. Both default to 30 days (2592000 seconds). The jwt.maxAge defines the maximum age of the NextAuth.js issued JWT in seconds.
You can use the getToken() helper function from 'next-auth/jwt'. Example: const token = await getToken({ req, secret }). The function accepts optional parameters including req, secret, secureCookie, and cookieName.
The default attributes are: httpOnly: true, sameSite: 'lax', path: '/', and secure: true (when using HTTPS/the __Secure- prefix in production).
Yes, if you have a custom jwt.decode method set in your NextAuth configuration, you must also pass the same decode method to withAuth middleware for it to work correctly.
The default refetchInterval is 0, which means the session is not polled automatically. If set to any value other than zero, it specifies in seconds how often the client should contact the server to update the session state.
The encode function receives an object with three parameters: { token, secret, maxAge }. The token is the JWT payload, secret is used for encryption, and maxAge defaults to session.maxAge (30 days).
The default value for session.updateAge is 24 hours (86400 seconds). However, this option is ignored when using JSON Web Tokens - it only applies to database session strategy.
NextAuth.js v4 introduced cookie chunking by default. Once cookies reach the 4kb limit, a new cookie is created with a {number} suffix and the cookies are reassembled in the correct order when parsing them.
By default, NextAuth.js tokens are both signed (JWS) using HS512 and encrypted (JWE). Since v4, all JWT tokens are encrypted by default. The encryption is in addition to signing to provide an extra layer of security.
Next.js App Router Integration > Server Actions
30 questionsThe jwt callback. During sign-in, it exposes the user's profile information from the provider, which you can use to add properties like user.id to the JWT token.
By default, the user is redirected to the current page (the page from which they initiated the sign-in process).
You must check for and re-throw NEXT_REDIRECT errors: if (error.digest?.startsWith('NEXT_REDIRECT')) throw error; - otherwise redirects will not work.
'use server' - This directive should be placed at the top of the file to mark all exports as Server Actions.
Return null for any missing or invalid data. Never throw errors from authorize(), as they will manifest as CallbackRouteError.
The auth() function. You can alias it: const auth = NextAuth(...); and call await auth() the same way you called getServerSession.
Set the redirectTo option: await signIn('github', { redirectTo: '/dashboard' })
As close to your data fetching as possible. Do not rely on middleware exclusively for authorization; always verify the session in Server Actions or Route Handlers.
The callbacks.authorized callback. Example: callbacks: { authorized: async ({ auth }) => { return !!auth } }
By default, only URLs on the same origin as the site are allowed for redirects. You can customize this using the redirect callback to prevent open redirect vulnerabilities.
useActionState. useFormStatus is deprecated as of Next.js 15, and you should import useActionState instead.
The redirect property defaults to true. When true, NextAuth automatically redirects after sign-in. When false, errors are logged but not thrown.
The auth() wrapper adds session information to the request object. Access it via req.auth in the middleware function.
The auth.ts file should export: auth, handlers, signIn, and signOut from the NextAuth() initialization.
Both 'CredentialsSignin' and 'CallbackRouteError' in the error.type switch statement should return user-friendly error messages like 'Invalid credentials.'
const [errorMessage, formAction, isPending] = useActionState(authenticate, undefined) - errorMessage is the error string or undefined, formAction is the action to bind to the form, and isPending is a boolean for loading state.
Export auth as middleware: export { auth as middleware } from '@/auth' in middleware.ts
The auth.ts file should be placed in the root of your repository. If using a src/ directory, it should be placed directly within the src/ folder.
async function authenticate(prevState: string | undefined, formData: FormData) - where prevState is the previous form state and formData contains the form inputs.
The route handler should be located at /app/api/auth/[...nextauth]/route.ts (or route.js for JavaScript).
The function signature changes to receive prevState or initialState as its first argument, followed by formData: async function(prevState: string | undefined, formData: FormData)
NextAuth throws an AuthError instance. For invalid credentials specifically, the error.type will be 'CredentialsSignin'.
A common pattern is to create an actions file like /app/lib/actions.ts with 'use server' at the top to mark all exports as Server Actions.
Use an inline async function with 'use server' directive: <form action={async () => { 'use server'; await signIn('github'); }}>
The token argument is only available when using the JWT session strategy, and the user argument is only available when using the database session strategy.
You should export GET and POST from the handlers: export const { GET, POST } = handlers
await signIn('credentials', formData) - where 'credentials' is the provider name and formData contains the email and password fields.
Import auth from your auth config and call it: const session = await auth(); - This replaces getServerSession in v5.
Set redirect: false in the options: await signIn('credentials', { ...credentials, redirect: false })
Testing
29 questionsCheck if process.env.NODE_ENV === "development" and only include the Credentials provider in the providers array when in development mode. You must be extremely careful not to leave insecure authentication methods available in production.
Yes, AUTH_SECRET is a required environment variable in production. If not set, NextAuth.js will throw an error.
import { Auth, customFetch } from "@auth/core"
import GitHub from "@auth/core/providers/github"
function proxy(...args: Parameters
return undici(args[0], { ...(args[1] ?? {}), dispatcher });
}
const response = await Auth(request, {
providers: [GitHub({ [customFetch]: proxy })]
})
database: { type: 'sqlite', database: ':memory:', synchronize: true }
cy.setCookie("next-auth.session-token", "a valid cookie from your browser session")
The customFetch option can be used to add mocks for testing, logging, support custom headers, cache discovery endpoints, and support corporate proxies.
await context.addCookies([{
name: "next-auth.session-token",
value: sessionToken,
domain: "localhost",
path: "/",
httpOnly: true,
sameSite: "Lax",
expires: 1661406204
}])
SQLite with the :memory: database creates an in-memory database that disappears whenever you restart the process, and synchronize option may cause data loss if the configured schema doesn't match the expected schema in production.
Run: npx auth secret - This command will autogenerate a random value and put it in your .env.local file.
jest.mock("next-auth", () => {
return {
__esModule: true,
...jest.requireActual("next-auth"),
getServerSession: () => ({
user: {
id: "123",
},
accessToken: "some token",
}),
};
});
error=CredentialsSignin&code=credentials (where code is configurable)
CredentialsSignin - This error appears when the authorize callback returns null in the Credentials provider.
It resolves into { error: "CredentialsSignin", status: 200, ok: true, url: null }
export default {
title: "Components/UserProfile",
component: UserProfile,
decorators: [
(Story, context) => (
),
],
} as Meta;
jest.mock("next-auth/react", () => {
const originalModule = jest.requireActual('next-auth/react');
const mockSession = {
expires: new Date(Date.now() + 2 * 86400).toISOString(),
user: { username: "admin" }
};
return {
__esModule: true,
...originalModule,
useSession: jest.fn(() => {
return {data: mockSession, status: 'authenticated'}
}),
};
});
Auth.js will automatically generate one when the server starts, but this dynamically generated secret won't persist across server restarts, meaning all signed tokens and sessions will become invalid every time your server restarts.
chromeWebSecurity: false must be set in the Cypress configuration to allow opening URLs at domains that aren't your page, enabling login to social providers.
Next.js App Router Integration > Server Components
29 questionsUse the NextAuthOptions type imported from 'next-auth': export const authOptions: NextAuthOptions = { ... }
The auth() function returns null when there is no authenticated session.
Check the session returned from auth() and use Next.js redirect(): if (!session) { return redirect('/api/auth/signin') }
Database adapters should be installed from the @auth/-adapter scope instead of @next-auth/-adapter. Example: @auth/prisma-adapter
No, you don't need to pass any parameters when calling await auth() in Server Components.
No, as of today there is no built-in solution for automatic refresh token rotation. Instead, you must use the jwt and session callbacks to persist OAuth tokens and refresh them when they expire.
In NextAuth v4, use getServerSession() from 'next-auth/next' to read sessions in Server Components, Route Handlers, API routes, or getServerSideProps.
AUTH_SECRET is a random token used to encrypt cookies and tokens, encode the JWT, and encrypt tokens and email verification hashes. It is mandatory and must be a cryptographically secure random string of at least 32 characters.
When used in getServerSideProps, pass context.req, context.res, and authOptions: await getServerSession(context.req, context.res, authOptions)
Both GET and POST methods must be exported. In v5: export const { GET, POST } = handlers. In v4: export { handler as GET, handler as POST }
In NextAuth v5, use the auth() function to read sessions in Server Components. This replaces getServerSession from v4.
No, auth() works server-side only and cannot be used directly in Client Components. For client components, use the useSession hook from 'next-auth/react' with SessionProvider.
Generate it via the CLI with 'npm exec auth secret' or via openssl with 'openssl rand -base64 33'
The NextAuth() function exports: auth, handlers, signIn, and signOut. Example: export const { auth, handlers, signIn, signOut } = NextAuth({ ... })
The Route Handler must be at: app/api/auth/[...nextauth]/route.ts (or .js)
When using JSON Web Tokens, the jwt() callback is invoked before the session() callback, allowing you to pass data from the JWT to the session.
The recommended way in v5 is to call auth() in the Server Component and pass the session down to Client Components as props if they need it, rather than using SessionProvider and useSession.
Yes, getServerSession requires passing the same authOptions object you would pass to NextAuth when initializing it.
Both the jwt callback (if using JWT session strategy) and the session callback are invoked when getServerSession(), auth(), or useSession() is called.
The session expiry value is rotated whenever the session is retrieved from the REST API or when the client contacts the backend, updating the session expiry date.
The expires value is stripped away from the session in Server Components because the underlying Next.js cookies() method only provides read access to request cookies.
The auth.ts file should be located at the root of your repository. If using the src/ directory approach, place it directly within the src/ folder.
The default properties are: name (string), email (string), and image (string). Only these three properties are exposed by default to prevent accidentally exposing sensitive user information.
Only the 'jwt' session strategy is currently supported when using Auth.js with Edge Runtime in middleware. The 'database' session strategy is not supported in edge environments.
getServerSession only returns a session object when a user has logged in (when authenticated cookies are present), otherwise it returns null.
getServerSession can drastically reduce response time by avoiding an extra fetch to an API Route. It directly accesses the session context without making an HTTP request, providing nearly 1000x performance improvement in some cases.
Install NextAuth v5 with the beta tag using: npm install next-auth@beta
Yes, the auth() function is asynchronous and must be awaited. Example: const session = await auth()
Session Management > Session Data
28 questionsWhenever a session is checked, including calls to getSession(), getServerSession(), useSession(), and /api/auth/session.
JWT strategy: { session, token }. Database strategy: { session, user }. When using JWT sessions, the JWT payload (token) is provided. When using database sessions, the User object (user) from the database is provided.
It specifies a custom base path for NextAuth API routes when your application doesn't use the default /api/auth path. It must match the path where your NextAuth API routes are located (e.g., basePath="/custom-route/api/auth").
httpOnly: true, sameSite: 'lax', path: '/', and secure: true (for HTTPS URLs) or secure: false (for HTTP URLs like http://localhost:3000).
"database" - sessions are stored in the database with a session ID saved in an HttpOnly cookie.
By default, only a subset of the token is returned for increased security. The session data does not contain sensitive information such as the Session Token or OAuth tokens, containing only minimal data (name, email, image, expires) needed to display information about the signed-in user.
No. Every tab/window maintains its own copy of the local session state; the session is not stored in shared storage like localStorage or sessionStorage. Tabs sync via message passing handled by SessionProvider.
When you want to avoid checking the session twice on pages that support both server and client side rendering. By passing the session page prop to SessionProvider, you can skip the client-side session fetch on initial load.
true. When enabled, tabs/windows will automatically update session state when they gain or lose focus.
The SessionProvider automatically takes care of keeping the session updated and synced between tabs/windows. Any update in one tab/window triggers a message to other tabs/windows to update their own session state. It also syncs when windows gain or lose focus (if refetchOnWindowFocus is enabled).
First invocation (sign in): { token, user, account, profile, isNewUser }. Subsequent invocations: only { token }. The user, account, profile, and isNewUser arguments are only passed the first time after sign in.
"loading" | "authenticated" | "unauthenticated". Loading indicates the session is being fetched, authenticated means a valid session exists, and unauthenticated means no session exists.
When set to false, it prevents session polling when the device has no internet access (using navigator.onLine). By default, session polling keeps trying even when offline, which can cause sessions to be invalidated.
The
It allows you to access the contents of the JWT on the server side without handling JWT decryption/verification yourself. It can only be used server-side, looks for the JWT in cookies or the Authorization header, and automatically decrypts JWE tokens.
{ user: { name: string | null, email: string | null, image: string | null }, expires: string } where expires is an ISO 8601 date string.
When a JSON Web Token is created (at sign in) or updated (whenever a session is accessed). Requests to /api/auth/signin, /api/auth/session, and calls to getSession(), getServerSession(), useSession() will invoke this function, but only if using a JWT session.
0 (disabled). When set to 0, session polling does not occur. If set to any other value, it specifies in seconds how often the client should contact the server to update the session state.
No. The update() method won't sync between tabs as the refetchInterval and refetchOnWindowFocus options do. You need to rely on the built-in refetch mechanisms for cross-tab synchronization.
When you call the update() method from useSession(), the JWT callback is triggered with trigger: "update". You can use this to narrow down the type and handle updates differently from sign-in events.
getServerSession is exclusive to server-side environments and directly accesses the session context, avoiding the overhead of an API request. getSession makes an extra fetch to /api/auth/session even on the server. getServerSession is recommended for server-side use (Route Handlers, React Server Components, API routes, getServerSideProps).
A256GCM (AES-256 GCM) with direct key agreement (algorithm "dir"). The JWT is encrypted (JWE) by default, not just signed (JWS).
The value for refetchInterval should always be lower than the value of the session.maxAge option.
86,400 seconds (24 hours), calculated as 24 * 60 * 60. This option throttles how frequently to write to the database to extend a session and only applies to database session strategy (ignored for JWT).
It enables you to update the session data with a new object. When called, it triggers the JWT callback with trigger: "update" (for JWT strategy). The updated data must be handled in the jwt callback to persist changes.
The session callback must always return a session object. It cannot return null or undefined. Typically, you modify and return the session parameter passed to the callback.
API and Client Methods > signOut Method
27 questionsNextAuth's signOut endpoint requires the POST HTTP method. This is a security measure to prevent malicious links from triggering sign out without consent through simple GET requests.
In NextAuth v4, import signOut from 'next-auth/react': import { signOut } from "next-auth/react"
You can pass a callbackUrl parameter: signOut({ callbackUrl: 'http://localhost:3000/foo' })
Add the pages option in your NextAuth configuration with the signOut property: pages: { signOut: '/auth/signout' }. The custom page must be placed outside /pages/api which is reserved for API code.
In NextAuth v5 (Auth.js), signOut() returns Promise<R extends false ? any : never>. It signs out the user and, if using database strategy, removes the session from the database and invalidates the related cookie.
No, sending a POST request to /api/auth/signout (with csrfToken) from server-side does not produce the expected result, but it does work fine when called from the client.
You can configure a signOut event in the events object of your NextAuth configuration. Example: events: { async signOut({ token }) { /* custom logic */ } }. Events are asynchronous functions useful for audit logs, reporting, or handling side-effects.
When called with redirect: false, signOut() returns a Promise that resolves to an object { url: string } where url is the validated URL the user would've been redirected to otherwise.
The CSRF token is obtained from the /api/auth/csrf endpoint, which returns a JSON object with a csrfToken property.
By default, only URLs on the same origin as the site are allowed. The URL must be an absolute URL at the same hostname, or a relative URL starting with '/'. If it doesn't match, it will redirect to the homepage. You can customize this with the redirect callback.
In NextAuth v4, the default session token cookie name is next-auth.session-token.
No, in NextAuth v4 (Pages Router), NextAuth.signOut() only works client-side. The client-side method is imported from 'next-auth/react'.
No, events are only available on the server-side. They are configured in the NextAuth options on the server and cannot be used on the client-side.
NextAuth uses the 'double submit cookie method' for CSRF protection, which uses a signed HttpOnly, host-only cookie.
In NextAuth v5 (Auth.js), the session token cookie name changed to authjs.session-token.
Manually expiring the session token cookie will not trigger the signOut event. You need to use the proper signOut() function to trigger configured events and ensure proper cleanup.
When using a database session strategy, calling signOut() will delete the session from the database and invalidate/remove the related session cookie.
When you pass redirect: false to signOut(), the page will not reload. The session will be deleted, and the useSession hook is notified so any indication about the user will be shown as logged out automatically.
The CSRF token must be passed as a form variable named csrfToken in all POST submissions to any API endpoint, including /api/auth/signout.
Yes, the signOut() method handles CSRF tokens automatically. It fetches the CSRF token from /api/auth/csrf and includes it in the POST request to /api/auth/signout.
Yes, with NextAuth v5.0.0-beta.16 and later, you can signOut on the server side. The server-side signOut is exported from your NextAuth configuration: export const { handlers, auth, signIn, signOut } = NextAuth({ ... })
The event signature changed from signOut(tokenOrSession) to signOut({ token, session }), using a named parameters pattern instead of a single parameter.
You only need to use getCsrfToken() if you are not using the built-in signOut() method. The built-in signOut() method handles CSRF tokens automatically.
You can use either 'application/json' with body: JSON.stringify({ csrfToken, json: "true" }) or 'application/x-www-form-urlencoded' with body: new URLSearchParams({ csrfToken, callbackUrl }).
When using JWT strategy (default), Auth.js deletes the JWT from the cookies, destroying the session. However, if the user has the JWT saved elsewhere, it will remain valid (the server will accept it) until it expires.
No, the CredentialsProvider cannot use session.strategy = "database" -- it must use JWT strategy.
By default, when no callbackUrl is provided, NextAuth redirects to the baseUrl (typically the homepage), as determined by the NEXTAUTH_URL environment variable.
Next.js App Router Integration > Route Handler Setup
27 questions[...nextauth] is a catch-all dynamic route segment. The square brackets indicate a dynamic segment, and the three dots (...) make it catch-all, matching /api/auth/* and all subsequent segments (e.g., /api/auth/signin, /api/auth/callback/google, etc.).
No. In Next.js App Router, you cannot have both a route.ts file and a page.ts file at the same route segment level. They are mutually exclusive - use route.ts for API endpoints and page.ts for UI pages.
No. App Router route handlers do not support default exports. You must use named exports for HTTP methods (GET, POST, etc.). Using export default will cause a type error stating it's not a valid Route export field.
In v5, create a root auth.ts file that exports { auth, handlers, signIn, signOut } from NextAuth(). Then in app/api/auth/[...nextauth]/route.ts, simply import and export: import { handlers } from "@/auth" followed by export const { GET, POST } = handlers.
In v5, use the AUTH_ prefix (e.g., AUTH_SECRET, AUTH_URL). The NEXTAUTH_ prefix (NEXTAUTH_SECRET, NEXTAUTH_URL) is aliased for backward compatibility but AUTH_ is the preferred naming going forward.
Use npx auth secret to autogenerate a random value and add it to your .env.local file. Alternatively, use openssl rand -base64 32 or openssl rand -hex 32 to generate a 32-character random string manually.
The default basePath is /api/auth when using the next-auth package. With other Auth.js framework implementations, it defaults to /auth. This is where all NextAuth endpoints (signin, callback, signout, session, etc.) are exposed.
Yes. In NextAuth.js v4, NEXTAUTH_SECRET is mandatory in production to encrypt JWTs and hash email verification tokens. Without it, you'll get a MissingSecret error during production builds.
NextAuth.js needs both GET and POST handlers to function properly. GET handles session checks and OAuth callbacks, while POST handles sign-in/sign-out actions and callback processing. Missing either method will cause authentication flows to fail.
The route handler must be at app/api/auth/[...nextauth]/route.ts (or route.js for JavaScript). The file must be named route.ts or route.js - this is a Next.js App Router requirement.
The configuration file should be named auth.ts and placed in the root of your repository. If you have a src/ directory, it can be src/auth.ts. It can be named anything, but auth.ts is recommended since you'll import from it across your app.
Set the basePath option in your NextAuth configuration (default is /api/auth) and set NEXTAUTH_URL to include the custom path. When using a custom basePath, you must also pass it to <SessionProvider basePath="/custom-path/api/auth"> in your client components.
Install using npm install next-auth@beta (or pnpm add next-auth@beta, yarn add next-auth@beta, bun add next-auth@beta). The @beta tag is required for version 5.
NextAuth v5 (Auth.js v5) requires Next.js 14.0 as the minimum version.
NextAuth v4 requires Next.js 12.2.5 as the minimum version. It supports Next.js versions ^12.2.5 || ^13 || ^14 || ^15.
In v4, create the handler directly in app/api/auth/[...nextauth]/route.ts: const handler = NextAuth({ providers: [...] }) then export { handler as GET, handler as POST }. The configuration is contained in this file.
Import and use the NextAuthOptions type: import type { NextAuthOptions } from 'next-auth'. Then define your config as export const authOptions: NextAuthOptions = { ... }.
NextAuth automatically handles: /api/auth/signin (sign-in page), /api/auth/signout (sign-out), /api/auth/callback/* (OAuth callbacks), /api/auth/session (session object), /api/auth/csrf (CSRF token), and /api/auth/providers (list of configured providers).
Pages Router uses pages/api/auth/[...nextauth].ts (file directly in folder). App Router uses app/api/auth/[...nextauth]/route.ts (route.ts file inside the [... nextauth] folder). The App Router pattern follows Next.js 13+ Route Handlers conventions.
No, it's not strictly necessary in most environments. NextAuth v5 auto-detects the host based on request headers. However, if your Next.js app uses a custom base path, you should specify the full route to the API endpoint.
NextAuth.js automatically detects it's being initialized in a Route Handler by understanding that it's passed a Web Request instance, and returns a handler that returns a Response instance (Web Response API).
Both GET and POST exports are required. You must export both methods: export { handler as GET, handler as POST }. Route Handlers expect named exports for HTTP methods, not default exports.
No. NextAuth.js cannot use Edge Runtime for initialization. If your route handler is edge-by-default in Next.js App Router, explicitly opt out with export const runtime = 'nodejs' in your route file, especially when using database adapters or email providers.
In v4, configuration is in the route handler file and you export/pass authOptions around. In v5, configuration moves to a root auth.ts file that exports auth, handlers, signIn, and signOut, avoiding the need to pass authOptions throughout the application.
No. When deploying to Vercel, NextAuth automatically detects the host using System Environment Variables, so you don't need to explicitly set NEXTAUTH_URL. With other providers, you must set this environment variable.
No, it's not recommended. Placing authOptions in [...nextauth]/route.ts causes build errors because Next.js restricts Route Handler exports to only HTTP methods. Extract authOptions to a separate file (e.g., auth.ts) to avoid type errors.
The root auth.ts file exports four items from NextAuth(): auth (universal authentication method), handlers (GET and POST route handlers), signIn (function to sign in), and signOut (function to sign out).
OAuth Authentication > Provider Configuration
26 questions"oauth" - This requires manual configuration of authorization, token, and userinfo endpoints.
id (unique identifier for the provider), name (display name), type (must be 'oauth' or 'oidc'), and clientId. The clientSecret is also typically required unless using PKCE with token_endpoint_auth_method: 'none'.
Yes, for OIDC providers, the issuer parameter is required. For built-in providers, you usually only need to specify client id, client secret, and in case of OIDC, an issuer.
Use the userinfo configuration with url and request properties: userinfo: { url: 'https://provider.com/userinfo', async request(context) { /* custom fetch */ } }
false (disabled by default). Automatic account linking on sign in is not secure between arbitrary providers and is disabled by default.
"oidc" - This enables NextAuth.js to use the issuer for automatic discovery via the .well-known/openid-configuration endpoint.
[origin]/api/auth/callback/[provider], where [provider] is the id of your provider. For example: http://localhost:3000/api/auth/callback/twitter or https://next-auth-example.vercel.app/api/auth/callback/google
It ensures the OAuth consent screen appears every time the user authenticates, even if they have previously granted permissions.
It specifies the OIDC discovery endpoint URL (typically ending in /.well-known/openid-configuration) which automatically discovers the authorization and token endpoints. For Google: "https://accounts.google.com/.well-known/openid-configuration"
profile(profile: P, tokens: TokenSet): Awaitable
Yes, even if you are using a built-in provider, you can override any of the default configuration options. For example, you can customize authorization params, scope, or other settings.
It tells NextAuth.js to decode the id_token to get user information instead of making an additional request to the userinfo endpoint.
POST method with URLSearchParams (application/x-www-form-urlencoded format). Example: body: new URLSearchParams({ client_id, client_secret, grant_type, refresh_token })
Yes, you can add as many OAuth providers as you like, as providers is an array. You can mix OAuth, Email, and Credentials providers in any order.
It allows customization of HTTP settings like timeout, headers, and agent for OAuth provider requests. Passed to openid-client's setHttpOptionsDefaults(). Useful for corporate proxies and custom HTTP agents.
OAuth 1.0, 1.0A, 2.0 and OpenID Connect, with built-in support for most popular sign-in services.
"none", "state", "nonce", or "pkce" - for example: checks: ["pkce", "state"]
authorization: { params: { prompt: "consent", access_type: "offline", response_type: "code" } } - This ensures the consent screen appears every time and forces Google to re-issue a Refresh Token.
Use authorization.params.scope: authorization: { params: { scope: "openid custom_scope" } }
AUTH_{PROVIDER}_{ID|SECRET|ISSUER} where PROVIDER is the uppercase snake case version of the provider's id. For example: AUTH_GITHUB_ID, AUTH_GITHUB_SECRET, AUTH_OKTA_ISSUER
Use the token.request() configuration option in your provider config to send JSON instead: token: { async request(context) { /* custom fetch with JSON body */ } }
colorScheme ("auto", "dark", or "light"), brandColor (custom accent color), and logo (URL to logo image rendered above the primary card)
Security > Secret Management
26 questionsChanging NEXTAUTH_SECRET in production invalidates all existing sessions, logging out every user. You should plan secret rotations during maintenance windows and communicate to users.
NextAuth uses the __Host- prefix for CSRF tokens when useSecureCookies is true. The __Host- prefix is stricter than __Secure- and requires the cookie to have a Path of '/' and must not have a Domain attribute.
You can use 'openssl rand -base64 32' to generate a random secret value. Alternatively, you can run 'npx auth secret' in your project's root, and it will autogenerate a random value and put it in your .env.local file.
The NEXTAUTH_SECRET is used to encrypt the NextAuth.js JWT, and to hash email verification tokens. It is also used to hash tokens, sign/encrypt cookies and generate cryptographic keys.
Yes, you can pass an array of secrets, in which case the first secret that successfully decrypts the JWT will be used. This is useful for rotating secrets without invalidating existing sessions - the newer secret should be added to the start of the array, which will be used for all new sessions.
useSecureCookies defaults to true for all site URLs that start with https:// and defaults to false on URLs that start with http:// (e.g. http://localhost:3000) for developer convenience.
Yes, in production, AUTH_SECRET (or NEXTAUTH_SECRET) is a required environment variable - if not set, NextAuth.js will throw an error.
AUTH_SECRET is an alias for NEXTAUTH_SECRET and is the preferred naming going forward. Both AUTH_SECRET and AUTH_URL are aliased for NEXTAUTH_SECRET and NEXTAUTH_URL for consistency.
For the sessionToken cookie, the default options are: httpOnly: true, sameSite: 'lax', path: '/', secure: true (on HTTPS sites).
The default maxAge is 30 days, calculated as 30 * 24 * 60 * 60 seconds. This applies to both the JWT token (jwt.maxAge) and the session cookie (session.maxAge).
No, you do not need the NEXTAUTH_URL environment variable in Vercel. However, the NEXTAUTH_SECRET is still required.
Yes, NextAuth stores JWTs in HttpOnly cookies by default, which is the most secure option as it prevents JavaScript access to the tokens.
No, JWT sessions work without a database and scale infinitely, but cannot be invalidated before expiration—a security concern if an attacker steals a token.
No, never commit this file—losing the secret invalidates all existing sessions. The .env.local file should be added to .gitignore.
NextAuth uses HKDF (HMAC-based Key Derivation Function) with SHA256. The configuration is: algorithm: SHA256, length: 32, info: 'NextAuth.js Generated Encryption Key' (v4) or 'Auth.js Generated Encryption Key ({salt})' (v5).
Yes, you can override the default JWT behavior using the encode and decode methods. Both methods must be defined at the same time. You can use custom JWT encode/decode functions with libraries like jsonwebtoken.
No, in development, the secret is optional. If not provided, NextAuth generates a secret based on the configuration for convenience, but this is volatile. However, NextAuth will likely make this option mandatory, even in development, in the future.
NextAuth.js uses encrypted JSON Web Tokens (JWE) by default. By default tokens are not signed (JWS) but are encrypted (JWE) using the A256CBC-HS512 algorithm in newer versions (v5), while older versions used A256GCM encryption.
If you rely on the default secret generation in development, you might notice JWT decryption errors since the secret changes whenever you change your configuration. Defining an explicit secret will make this problem go away.
When using an array of secrets, the first element (index 0) is used to sign/encrypt new JWTs, while all secrets in the array are tried for decryption until one successfully decrypts the JWT. This allows gradual migration without invalidating all existing user sessions.
In NextAuth v4, the info string for HKDF was 'NextAuth.js Generated Encryption Key' with an empty salt. In Auth.js v5, the info string was updated to 'Auth.js Generated Encryption Key ({salt})' where the salt is typically the cookie name.
To mitigate risks with the 'jwt' strategy, Auth.js libraries store the refresh_token in an encrypted JWT, in an HttpOnly cookie.
When set to true, all cookies set by NextAuth.js will only be accessible from HTTPS URLs. It sets the 'secure' flag and also adds '__Secure-' prefix to cookie names. The CSRF token specifically uses the stricter '__Host-' prefix.
NextAuth has built-in defenses against replay attacks, clickjacking, and OAuth state tampering enabled by default.
The recommended length is at least 32 characters. The official documentation examples use 'openssl rand -base64 32' which generates a 32-character base64-encoded string. For Hash-based Message Authentication Code algorithms (e.g. HS256 or HS512), you must use a secret key which has the same (or larger) bit length as the size of the output hash.
Setting useSecureCookies to false in production is a security risk and may allow sessions to be hijacked. It is intended to support development and testing only, and using this option in production is not recommended.
Email Authentication > Email Provider Setup
26 questionsNo, nodemailer fails with Vercel Edge Runtime due to Node.js 'stream' dependency. The edge runtime does not support Node.js 'stream' module.
NextAuth has built-in HTTP email providers including Resend, SendGrid, Postmark, and Nodemailer.
The default theme colors are brandColor: "#346df1" (blue) and buttonText: "#fff" (white).
Yes, SMTP traffic on port 25 is blocked on Vercel, which can cause issues when deploying NextAuth applications using the default SMTP configuration.
No, NextAuth.js does not include nodemailer as a dependency. You must install it yourself by running npm install nodemailer or yarn add nodemailer.
NextAuth documentation recommends checking out https://mjml.io for generating great looking email client compatible HTML with React.
By default, the verification token is valid for 24 hours. If the user clicks the link within that time, an account is created and they are signed in. Otherwise, the token expires and they must request a new one.
By default, NextAuth generates a random verification token using randomBytes(32).toString('hex'), which creates a 64-character hexadecimal string.
No, the request parameter was added to @auth/[email protected] and will be part of NextAuth v5, but it is not available in NextAuth v4.
By default, NextAuth.js normalizes email addresses by treating them as case-insensitive (converts to lowercase), which is technically not compliant with RFC 2821 spec. It also removes any secondary email address that was passed in as a comma-separated list.
No, the Email Provider can be used with both JSON Web Tokens and database sessions, but you must configure a database to use it. It is not possible to enable email sign in without using a database.
You can use EMAIL_SERVER (connection string) or individual variables: EMAIL_SERVER_USER, EMAIL_SERVER_PASSWORD, EMAIL_SERVER_HOST, EMAIL_SERVER_PORT, and EMAIL_FROM.
The default maxAge is 24 hours (24 * 60 * 60 seconds), which controls how long email verification links remain valid.
Yes, the built-in sendVerificationRequest() method sends emails with both text and html parameters for email client compatibility.
You can either use a connection string (e.g., smtp://username:[email protected]:587) or a nodemailer configuration object with individual properties (host, port, auth).
The verification_requests table was renamed to verification_tokens in NextAuth v4.
The default settings are { host: "localhost", port: 25, auth: { user: "", pass: "" } }.
The two required parameters are server (SMTP server connection string or configuration object) and from (email address to send from).
The function receives a params object with: identifier (email address), url (verification link), expires (Date when token expires), provider (provider configuration), token (verification token), and theme (theming options).
Yes, you can fully customize the sign in email by passing a custom function as the sendVerificationRequest option to EmailProvider().
The VerificationToken model contains: identifier (String), token (String @unique), expires (DateTime), with a unique constraint on [identifier, token].
The default normalizeIdentifier splits by '@', then takes only the first element from the domain part split by ',', effectively removing secondary email addresses passed as comma-separated lists.
The format is smtp://username:[email protected]:587. For example, with SendGrid: smtp://apikey:[email protected]:587.
Yes, you can define a generateVerificationToken method in your provider options to override the default random token generation.
Yes, you can apply your own normalization via the normalizeIdentifier method on the EmailProvider to override the default case-insensitive behavior.
TypeScript
25 questionsThe useSession hook returns SessionContextValue
In NextAuth v5, you should declare module '@auth/core' for the User interface instead of 'next-auth'.
No. There is no need to install @auth/core to import provider definitions - these come from next-auth itself in v5.
NextAuth v3 used 'next-auth/client' for client-side imports. In v4, this was renamed to 'next-auth/react' to better reflect that it can be used with pure React apps, not just Next.js.
When using JSON Web Tokens, the jwt() callback is invoked before the session() callback, so anything you add to the JSON Web Token will be immediately available in the session callback.
Adding fields to JWT will increase the size of the nextauth cookie and this will be included in every request to your application.
You can extend the following interfaces: Session, User, Account, Profile, JWT, and AdapterUser. Session and User are the most commonly extended interfaces.
The main configuration interface in NextAuth v4 is called NextAuthOptions, which is imported from 'next-auth'.
The default session user object contains three properties: name, email, and image. The session also includes an expires property for the session expiry date.
The status property can be "authenticated", "loading", or "unauthenticated".
In v4, you import getServerSession from 'next-auth/next' and pass it the authOptions configuration: import { getServerSession } from 'next-auth/next'. You then call it with context.req, context.res, and authOptions.
The recommended file path is types/next-auth.d.ts or next-auth.d.ts in your project root. This file must be included in your tsconfig.json's 'include' array for TypeScript to recognize the type augmentations.
By default, TypeScript will merge new interface properties and overwrite existing ones. When you override the Session interface, the default session user properties (name, email, image) will be overwritten with your new definition, so you need to explicitly add them back if you want to keep them.
You need to extend DefaultSession["user"] when adding custom properties. Example: interface Session { user: { customProp: string } & DefaultSession["user"] }. This preserves the default name, email, and image properties.
When using JSON Web Tokens for sessions, the session callback receives session and token parameters. The JWT payload (token) is provided instead of the user object.
No. Even if you don't use TypeScript, IDEs like VSCode will pick up NextAuth's built-in type definitions to provide you with a better developer experience through autocomplete and IntelliSense.
The jwt callback receives token, account, and profile parameters. The contents of user, account, profile and isNewUser will vary depending on the provider and if you are using a database.
You should declare module 'next-auth/jwt' when extending the JWT interface in v4. The JWT type is not exported from 'next-auth', only from 'next-auth/jwt'.
In NextAuth v5, you should declare module '@auth/core/jwt' when extending the JWT interface.
You should declare module 'next-auth' when extending the Session interface in v4. Example: declare module 'next-auth' { interface Session { ... } }
No. NextAuth.js comes with its own type definitions built-in, so you don't need to install a separate @types/next-auth package. The @types/next-auth package at DefinitelyTyped is now deprecated and not maintained anymore - it's just a stub that points to the main package.
In v5, instead of importing getServerSession from 'next-auth/next', you import the auth function from your config file (e.g., '@/auth'). The universal auth() method replaces getServerSession, getSession, withAuth, getToken for server-side usage.
AdapterUser extends User, so you can set custom user attributes once in the User interface and everything else should work properly. AdapterUser is used by database adapters.
You should import DefaultSession from 'next-auth': import { DefaultSession } from 'next-auth'
Custom Pages > Sign In Page
25 questionsPass it as part of the options object (second parameter). Example: signIn('provider', { callbackUrl: '/dashboard' })
next-auth/react. Example: import { getProviders, getCsrfToken, signIn } from 'next-auth/react'
Promise<null | Record<ProviderId, ClientSafeProvider>>. It returns null if no providers are available or an error occurs, otherwise an object mapping provider IDs to provider configuration objects.
It prevents automatic redirection and instead returns an object with properties including error, status, ok, and url, allowing you to handle errors or custom redirect logic yourself.
No. Unlike getCsrfToken(), when calling getProviders() server side, you don't need to pass anything, just as calling it client side.
So that the next-auth Middleware is aware of your custom pages, so it won't end up redirecting to itself when an unauthenticated condition is met.
csrfToken (obtained from /api/auth/csrf) and email (the email address for sign-in)
The callbackUrl defaults to the page URL the sign-in is initiated from.
null. If set, new users will be directed to this page on first sign in.
NextAuth uses its built-in pages at /api/auth/signin, /api/auth/signout, /api/auth/error, and /api/auth/verify-request. The newUser page defaults to null.
By default, it requires the URL to be an absolute URL at the same host name, or you can also supply a relative URL starting with a slash. Only URLs on the same origin as the site are allowed.
error (string | undefined), status (number for HTTP status code), ok (boolean indicating if signin was successful), and url (string | null, null if error, otherwise the redirect URL)
Yes. The signIn() method will handle CSRF Tokens for you automatically when signing in with email. This is the recommended approach.
signIn, signOut, error, verifyRequest, and newUser. Example: pages: { signIn: '/auth/signin', signOut: '/auth/signout', error: '/auth/error', verifyRequest: '/auth/verify-request', newUser: '/auth/new-user' }
Configuration (there is a problem with the server configuration) and AccessDenied (usually occurs when you restricted access through the signIn callback or redirect callback)
It is used for the check email message page, typically shown after email sign-in is initiated.
Use the pages option with the signIn property. Example: pages: { signIn: '/auth/signin' }
Error codes are passed in the query string as ?error=. Example: /auth/error?error=Configuration
id (string), name (string), type (ProviderType), signinUrl (string), and callbackUrl (string)
matcher: "/dashboard/:path*". This protects sub pages like /dashboard/settings and /dashboard/profile.
You must pass the context object. Example: const csrfToken = await getCsrfToken(context)
The import changed from 'next-auth/client' to 'next-auth/react', and functions were renamed: csrfToken became getCsrfToken, providers became getProviders, and signin became signIn.
Session Management > Database Sessions
25 questionsWhen using "database" strategy, the session cookie only contains a sessionToken value, which is used to look up the session in the database.
The Prisma adapter supports MySQL, PostgreSQL, MongoDB, and SQLite.
In development (HTTP), the default session cookie name is next-auth.session-token (without the __Secure- prefix).
getServerSession can provide up to 100% (2X) speedup compared to getSession because it directly accesses the session logic without making an HTTP request.
Yes. When using the Email provider, it is mandatory to also use a database adapter because verification tokens need to be persisted longer term for the magic link functionality to work.
The default maxAge is 30 days, specified as 30 * 24 * 60 * 60 seconds (2592000 seconds).
When you use an adapter, NextAuth defaults the session strategy to "database" instead of the default "jwt" strategy.
Setting updateAge to 0 will always update the database on every request that checks the session, which can create substantial database load for high-traffic applications.
No. As of v4, NextAuth.js no longer ships with an adapter included by default. If you would like to persist any information, you need to install one of the many available adapters yourself.
Yes. You can still force a JWT session by explicitly defining session: { strategy: "jwt" } even when using an adapter.
The recommended behavior is onDelete: Cascade, which automatically deletes all associated Session records when a User is deleted.
getServerSession should be used instead of getSession for server-side session retrieval because it avoids an extra HTTP fetch call, resulting in significantly better performance.
The session.jwt: boolean option was renamed to session.strategy: "jwt" | "database" in NextAuth v4.
In production (HTTPS), the default session cookie name is __Secure-next-auth.session-token.
The default httpOnly setting is true, which prevents JavaScript from accessing the cookie client-side.
No. Unless your database and its adapter is compatible with the Edge runtime, you cannot use the "database" session strategy. Middleware only supports the "jwt" session strategy.
The default updateAge is 24 hours, specified as 24 * 60 * 60 seconds (86400 seconds).
No. The Credentials provider can only be used if JSON Web Tokens are enabled for sessions. Users authenticated with the Credentials provider are not persisted in the database by default.
The four required fields are: id (String, primary key), sessionToken (String, unique), userId (String), and expires (DateTime).
When a Session is read, NextAuth checks if its expires field indicates an invalid session and deletes it from the database. However, this automatic cleanup doesn't always work reliably, and a periodic background cleanup job may be needed.
The session.strategy option accepts two exact string values: "jwt" or "database".
Auth.js with database session strategy makes many calls to the database during normal operations. The Edge runtime will be trying to execute, for example, PostgreSQL queries in an environment where the underlying functionality is not available (i.e. TCP sockets).
When using database sessions, the session is deleted from the database and the session ID is deleted from cookies when a user signs out.
The updateAge option (in seconds) throttles how frequently to write to the database to extend a session. It is used to limit write operations and is ignored if using JSON Web Tokens.
OAuth Authentication > Profile Mapping
25 questionsIf you use uncommon fields like GitHub's refresh_token_expires_in, make sure to return it via the new account() callback in v5.
No, a userinfo endpoint is not part of the OAuth specification, but is usually available for most providers. It returns information about the logged-in user.
The profile option has type ProfileCallback
Setting idToken: true allows NextAuth to decode the id_token to get user information instead of making an additional request to the userinfo endpoint. However, when specifying idToken: true, the userinfo endpoint is no longer used automatically.
Google's sub field (subject identifier) should be mapped to the id field. Example: id: profile.sub
The source of truth for a consistent, unique user id from a provider is in account.providerAccountId, which you can access in the jwt callback and signin callback. For Google, providerAccountId corresponds to the sub field.
First, add the custom column to the User model in your database schema. Then modify the TypeScript User interface by creating a next-auth.d.ts file, and add the new field to the profile() callback for each provider you are using.
The id is expected to be returned as a string type. For example, if your provider returns it as a number, you can cast it by using the .toString() method.
When using a database, the User object will be either a user object from the database (including the User ID) if the user has signed in before, or a simpler prototype user object (i.e. name, email, image) for users who have not signed in before.
You can use a custom request function that accepts a context parameter with the tokens. Example: userinfo: { url: 'https://...', async request(context) { return await fetch(url, { headers: { Authorization: Bearer ${context.tokens.access_token} }}) } }
The profile callback is expected to return these four fields: id, name, email, and image. This standardization makes the returned profile object comply across all providers/accounts/adapters.
Yes, the profile callback receives both the profile and tokens parameters. The tokens parameter contains OAuth tokens like access_token, refresh_token, and id_token from the TokenSet.
For OpenID Connect (OIDC) compliant providers, NextAuth.js recommends using the wellKnown option, which allows OIDC compliant providers to be set up without further configuration of authorize/token/userinfo options in most cases.
Without a database, the user object will be a simpler prototype user object with information extracted from the profile, typically containing name, email, and image.
The arguments user, account, profile and isNewUser are only passed the first time this callback is called on a new session, after the user signs in. In subsequent calls, only token will be available.
The profile callback is used to transform the OAuth provider's user profile into NextAuth.js's standard user format, returning fields like id, name, email, and image.
The callback URL should be set in your provider's dashboard to https://app.com/{basePath}/callback/{id}, where basePath defaults to /api/auth for Next.js.
The only truly required field is id to be able to identify the account when added to a database.
Yes, you can override any of the built-in provider options including the profile callback to return the profile in a different shape than the default.
If any of the standard fields (id, name, email, image) are missing values from the OAuth provider, they should be set to null.
Use an if branch to check for the existence of parameters like account or profile (apart from token). If they exist, this means that the callback is being invoked for the first time (i.e. the user is being signed in).
The profile callback should return 'image' as the standard field name. Many OAuth providers return 'picture', which should be mapped to 'image' in the callback. Example: image: profile.picture
You can configure custom scopes using the authorization object with params. Example: authorization: { params: { scope: "openid your_custom_scope" } }
Yes, the profile callback can be async and make calls to external resources if necessary.
When using a database, the profile() callback's return value is used to create users in the database.
Email Authentication > Verification Tokens
24 questionsThe createVerificationToken method receives a VerificationToken object with three fields: identifier (string), expires (Date), and token (string). The token provided is already hashed, so it just needs to be written to the database.
By default, NextAuth normalizes email addresses by converting them to lowercase and trimming whitespace. It treats values as case-insensitive (which is technically not RFC 2821 compliant, but in practice causes fewer problems when looking up users by email from databases). The default implementation uses identifier.toLowerCase().trim().split('@'), and also removes comma-separated secondary addresses from the domain part.
The NEXTAUTH_SECRET (or AUTH_SECRET in newer versions) environment variable is used to hash email verification tokens. This same secret is also used to encrypt NextAuth.js JWTs.
In NextAuth v3, the table was called verification_requests. It was renamed to verification_tokens in NextAuth v4 as part of the upgrade.
SHA-256 is recommended and considered sufficient for hashing verification tokens. The verification token can be a secure hash of a generated key using SHA-256.
The sendVerificationRequest function receives a params object with the following properties: identifier (string - the user's email), url (string - the verification URL), token (string - verification token), expires (Date - expiration date), provider (EmailConfig - provider configuration), theme (Theme - theming information), and request (Request - the HTTP request object).
Yes, the createdAt and updatedAt fields were removed from all Models, including VerificationToken, in NextAuth v4.
In newer versions of Auth.js, AUTH_SECRET is aliased for NEXTAUTH_SECRET for consistency. Both environment variable names are supported.
Two adapter methods are required: (1) createVerificationToken - creates a verification token and stores it in the database, and (2) useVerificationToken - retrieves the verification token from the database and deletes it so it can only be used once.
The VerificationToken model contains three required fields: identifier (String), token (String), and expires (DateTime). Some implementations may optionally include an id field.
NextAuth uses HKDF (HMAC-based Extract-and-Expand Key Derivation Function) with SHA-256 as the hashing algorithm. The implementation calls hkdf with 'sha256', the secret, an empty salt, 'NextAuth.js Generated Encryption Key' as info, and 32 bytes as the key length.
Yes, in production, NEXTAUTH_SECRET (or AUTH_SECRET) is a required environment variable. If not set, NextAuth.js will throw an error. Not providing any secret or NEXTAUTH_SECRET will throw an error in production.
By default, verification tokens are valid for 24 hours. If the verification token is used within that time (by clicking on the link in the email), an account is created for the user and they are signed in.
Yes, you can define a generateVerificationToken method in your provider options to override the default behavior. By default, NextAuth generates a random verification token, but you can provide a custom function to generate tokens according to your requirements.
The identifier field stores the email address (or other user identifier) of the user requesting authentication. This field is used to associate verification tokens with specific users during the email-based authentication process.
NextAuth does not have built-in automatic scheduled cleanup. However, developers can implement cleanup logic in the useVerificationToken adapter method to delete expired tokens when verification tokens are checked or consumed. Some implementations delete every verification token which has an expires timestamp in the past.
The useVerificationToken method has the responsibility to retrieve the verification token from the database and delete it so it can only be used once. It should return the verification token object if valid, or null/undefined otherwise. Most ORMs support retrieving the value while deleting it at the same time, reducing database calls.
NextAuth uses AES-256-GCM encryption algorithm for JSON Web Tokens (JWE). By default, tokens are encrypted (JWE) rather than just signed (JWS), providing an additional layer of security.
Yes, a database is required for passwordless login to work as verification tokens need to be stored. It is not possible to enable email sign in without using a database. You must configure one of the database adapters for storing the email verification token.
Yes, you can apply your own normalization via the normalizeIdentifier method on the EmailProvider. This allows you to customize how email addresses are processed before verification tokens are created.
You can generate a random value for NEXTAUTH_SECRET using the command 'openssl rand -base64 32' or by visiting https://generate-secret.vercel.app/32.
Verification tokens are hashed before being stored in the database. The token is hashed before being passed to createVerificationToken, which prevents decoding the token. The NEXTAUTH_SECRET environment variable is used to hash email verification tokens.
Yes, the 'from' field is a required parameter when setting up the email provider. It specifies the sender email address and must be a verified sender address with your email service provider. Example configuration: EmailProvider({ server: process.env.EMAIL_SERVER, from: process.env.EMAIL_FROM }).
The verification_tokens table has two unique constraints: (1) a unique constraint on the token field itself (@unique), and (2) a composite unique constraint on the combination of identifier and token fields (@@unique([identifier, token])).
Database Adapters > MongoDB Adapter
24 questionsTo preserve the value across module reloads caused by HMR (Hot Module Replacement) and prevent creating multiple connections during development
No, in production mode it's best to not use a global variable and create a new MongoClient instance
No, the Credentials provider can only be used if JSON Web Tokens are enabled for sessions (session.strategy: 'jwt')
@@unique([provider, providerAccountId]) - the combination of provider and providerAccountId fields must be unique
It's a callback function (client) => Promise
Yes, in MongoDB, collections and indexes are created automatically when using the NextAuth MongoDB adapter
All timestamps are stored as ISODate() in MongoDB and all date/time values are stored in UTC
A sparse index is used to allow email to be optional while still enforcing uniqueness if it is specified
No, the MongoDB adapter does not handle connections automatically. You must pass the adapter a MongoClient that is already connected.
It handles ObjectId conversions with two functions: 'from' (converts MongoDB object to plain JavaScript object) and 'to' (converts plain JavaScript object to MongoDB object with _id: ObjectId)
id, userId, type, provider, providerAccountId, refresh_token, access_token, expires_in, expires_at, token_type, scope, id_token, session_state
@auth/mongodb-adapter (not @next-auth/mongodb-adapter which was for v4)
collections (object with Accounts, Sessions, Users, VerificationTokens properties), databaseName (string), and onClose (callback function)
id (ObjectId), sessionToken (unique), userId, expires (DateTime), and user relation
Yes, users report using @auth/mongodb-adapter version 2.0.10 with mongodb package version 6.3.0 successfully
API and Client Methods > useSession Hook
24 questionstrue. By default, session polling will keep trying even when the device has no internet access.
No. The update() method does not sync between tabs as the refetchInterval and refetchOnWindowFocus options do.
onUnauthenticated(). This callback is executed if the user is not authenticated, allowing you to define custom behavior instead of the default redirect to sign-in.
trigger: "update". When using strategy: "database", the update() method triggers the session callback with trigger set to "update".
React Context. SessionProvider allows instances of useSession() to share the session object across components by using React Context.
It was removed in favor of refetchInterval. The clientMaxAge option from v3 was replaced by refetchInterval in v4.
Yes. The SessionProvider must be added to _app.js (or the root layout) before using useSession(). If not wrapped, you'll get an error: "[next-auth]: useSession must be wrapped in a
trigger: "update". When using strategy: "jwt", the update() method triggers the jwt callback with trigger set to "update".
Version 4.21.1 or later. The update method was not available in version 4.20.1 and earlier.
useSession won't show a loading state, as it'll already have the session available. This avoids checking the session twice on pages that support both server and client side rendering.
Yes. The data object is coming from the client, so it needs to be validated on the server before saving. The official documentation emphasizes this validation requirement.
undefined (when the session hasn't been fetched yet), null (if it failed to retrieve the session), or a Session object (when authenticated).
0 (zero), which means there will be no session polling by default.
Yes. When called without arguments, the session will be reloaded from the server without sending any data to update.
No. SessionProvider uses React Context which is unavailable in Server Components. You must create a client component wrapper with "use client" directive to use SessionProvider.
Every tab/window maintains its own copy of the local session state (not stored in localStorage or sessionStorage). Any update in one tab/window triggers a message to other tabs/windows using storage events by writing data to localStorage, which other tabs listen for.
The user is redirected to the sign-in page (default: /api/auth/signin), and after a successful login, they will be sent back to the page they started on.
{ user: { name: string, email: string, image: string }, expires: Date }
"loading", "authenticated", or "unauthenticated". These map to three possible session states.
true. By default, SessionProvider automatically refetches the session when the user switches between windows.
update(data?: any): Promise<Session | null>. It can be used to update the session without reloading the page.
No. These options have no effect on clients that are not signed in.
Seconds. For example, refetchInterval={5 * 60} would re-fetch the session every 5 minutes.
Configuration Options > Environment Variables
24 questionsAUTH_SECRET must be set in production, or Auth.js will throw an error. It should be a cryptographically secure random string of at least 32 characters, used to encrypt cookies and tokens.
AUTH_ prefix. The documentation states: '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.' NEXTAUTH_ prefix is no longer recommended.
AUTH_{PROVIDER}_{ID|SECRET}, where PROVIDER is the uppercase snake case version of the provider's id. For example: AUTH_GITHUB_ID and AUTH_GITHUB_SECRET for GitHub, or AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET for Google.
NextAuth automatically sets secure cookies to false in development (when NODE_ENV=development or URLs start with http://) and true in production (when NODE_ENV=production or URLs start with https://), unless NEXTAUTH_URL explicitly contains an HTTPS URL.
No, it's mostly unnecessary with v5 as the host is inferred from the request headers. However, when deploying to production, you may still want to set it to the canonical URL of your site for consistency.
openssl rand -base64 33. The current official Auth.js documentation recommends this command. Alternatively, you can use 'npm exec auth secret' (or 'npx auth secret').
The AUTH_SECRET value needs to be the same for both the stable deployment and preview deployments that need OAuth support. The redirect proxy security relies on the same server-side AUTH_SECRET being used across deployments.
No. You do not need the NEXTAUTH_URL environment variable in Vercel. Auth.js automatically detects the deployment URL using Vercel's System Environment Variables. Make sure 'Automatically expose System Environment Variables' is checked in your Project Settings.
False for URLs starting with http:// (e.g., http://localhost:3000) and true for URLs starting with https://. This happens automatically for developer convenience in development while maintaining security in production.
Yes. If you are using the Essential Next.js Build Plugin, you do not need to set the NEXTAUTH_URL environment variable as it is set automatically as part of the build process. You still need to set NEXTAUTH_SECRET manually.
It enables OAuth callback request redirection to preview deployment URLs. Auth.js requires a stable deployment URL that redirects OAuth callbacks to preview URLs when AUTH_REDIRECT_PROXY_URL is set. The flow saves the preview URL in the state parameter but sets redirect_uri to the stable deployment.
In development: a SHA hash of the options object. In production: no default (it's required). The secret is required in production and NextAuth will throw an error if not set.
Apple. The Apple provider does not support redirect proxy usage. This is a known limitation explicitly documented in the Auth.js deployment documentation.
Yes. In Auth.js v5, provider credentials can be automatically inferred if environment variables follow the AUTH_{PROVIDER}ID and AUTH{PROVIDER}_SECRET pattern. For example, AUTH_GITHUB_ID and AUTH_GITHUB_SECRET are automatically used as clientId and clientSecret for the GitHub provider.
Use http:// for localhost development (e.g., http://localhost:3000) and https:// for production. The format is NEXTAUTH_URL=
At least 32 characters. The documentation states: 'We recommend at least a 32 character random string.'
It's not recommended. The $ character has been reported to cause issues in certain deployment environments (particularly Vercel), likely due to shell variable expansion. Other special characters like @!*& work without problems. If your generated secret contains $, regenerate it or replace that character.
It autogenerates a random AUTH_SECRET value and adds it to your .env.local file (or .env file depending on framework conventions). This is the official Auth.js CLI command for generating secrets.
When set to true, it tells Auth.js to trust the X-Forwarded-Host and X-Forwarded-Proto headers passed by a reverse proxy to auto-detect the host URL. This is necessary when running Auth.js behind a proxy or in Docker environments.
AUTH_SECRET is an alias for NEXTAUTH_SECRET and is the preferred naming going forward. Both work in v5 for backward compatibility, but AUTH_SECRET is the recommended variable name for new projects.
Both. Starting with beta 16, AUTH_SECRET is required during the build process. Previously (beta 15 and earlier), it was only required at runtime. The build will fail with 'Missing secret, please set AUTH_SECRET or config.secret' if not set during build.
It's used for server-side calls instead of NEXTAUTH_URL when the server doesn't have access to the canonical URL of your site. It defaults to NEXTAUTH_URL if not provided. This is useful in Kubernetes/container environments where the server can't reach the public URL but can call localhost:3000.
Vercel and Cloudflare Pages (CF_PAGES). Auth.js automatically infers AUTH_TRUST_HOST to be true when it detects the VERCEL or CF_PAGES environment variables.
The documentation mentions https://generate-secret.vercel.app/32 as a web-based tool to generate random values. However, it also recommends npx auth secret as the preferred CLI method.
OAuth Authentication > Scopes and Permissions
24 questionsThe default scope for the GitHub provider is "read:user user:email". This was changed from the previous "user" scope to use more restrictive read-only scopes. The "user" scope granted read/write access to profile info, but was replaced with scopes that request only read-only access.
The most common scope configuration for Microsoft Entra ID is "openid profile email User.Read". For refresh token support, you can add "offline_access": "openid profile email User.Read offline_access". For custom API scopes, the format is "api://
Incremental authorization is the ability to request additional OAuth scopes at runtime after initial sign-in. NextAuth.js does not have native support for this. The main challenge is that when users grant new scopes, the account in the database is not updated, and access tokens cannot be modified after issuance. Workarounds include: (1) triggering new sign-in with include_granted_scopes=true, (2) calling signIn() with new scope and prompt: "consent", or (3) using the profile callback to update the database with new tokens.
To obtain refresh tokens from Google, use: prompt: "consent", access_type: "offline", and response_type: "code". The full configuration is: authorization: { params: { prompt: "consent", access_type: "offline", response_type: "code" } }. Google only provides a refresh token the first time a user signs in unless prompt is set to "consent".
To override a provider's default scopes, you only need to override authorization.params.scope instead of the whole authorization option. For example: Auth0({ authorization: { params: { scope: "openid custom_scope" } } }). NextAuth.js deeply merges user-provided options with default provider config, so you only need to specify what differs.
NextAuth.js deeply merges user-provided options with default provider config options, meaning you only need to override the parts that differ. This feature was introduced in version 4.0.0-next.20 (2021-08-04). For example, if you only want to change scopes, you can override just authorization.params.scope without redefining the entire authorization object.
The prompt: "consent" parameter forces the consent screen to appear every time, which is useful for ensuring refresh tokens are issued. Without this, the consent screen may only appear on the first authorization, and subsequent sign-ins won't generate new refresh tokens.
NextAuth.js supports OAuth security checks through the checks parameter, which can include ["pkce", "state"]. These checks are configured on a per-provider basis. For example: checks: ["pkce", "state"]. These checks help prevent authorization code interception attacks.
OAuth scopes in NextAuth.js must be formatted as a space-separated string. For example: "openid email profile" or "repo user delete_repo". When using an array for readability, it should be joined with spaces using .join(" "), like: ["openid", "email", "profile"].join(" ").
NextAuth.js v5 (Auth.js) uses the AUTH_ prefix for environment variables instead of NEXTAUTH_. For example, AUTH_GITHUB_ID and AUTH_GITHUB_SECRET will be automatically inferred and used as the clientId and clientSecret options for the GitHub provider.
The default scope for the Google provider is "openid email profile". This is defined in the provider's source code at packages/core/src/providers/google.ts as authorization: { params: { scope: "openid email profile" } }.
To obtain a refresh token, you need to add "offline_access" to the authorization scope. This is the standard OAuth 2.0 scope for requesting offline access. For example, with Azure AD: "openid profile email offline_access", or with Okta: "openid email profile offline_access".
The access_type: "offline" parameter requests a refresh token from the OAuth provider (particularly Google). This allows the application to obtain new access tokens without requiring the user to re-authenticate. Google requires this parameter to provide refresh tokens.
In NextAuth.js v4, the single authorization option replaced the previous authorizationUrl, authorizationParams, and scope options. This provides a more unified way to configure OAuth provider authorization settings.
Yes, there have been reported issues where custom authorization.params.scope values are not respected for certain providers. Notable issues include GitHub provider (Issue #3153) where debug logs showed custom scopes being ignored, and Discord provider in v5 where the default scope was used instead of the custom scope. There are also reports of scope parameters getting lost after authorization call and during OAuth callback (Issue #12390).
NextAuth.js versions before v4.20.1 had a vulnerability where attackers could intercept the returned authorization URL and strip away the OAuth checks (nonce, state, pkce). This allowed bypassing security validations. The recommended fix is upgrading to the latest version. Developers can also manually check the callback request for state, pkce, and nonce against the provider configuration using Advanced Initialization.
Yes, you can add as many OAuth providers as you like with different scopes for each, as providers is an array. Each provider can have its own authorization.params.scope configuration. For example, you can configure Google with "openid email profile" and Auth0 with "openid custom_scope" in the same providers array.
The default code_challenge_method for PKCE in NextAuth.js is "S256" (SHA-256). The code_verifier is saved in a cookie that expires after 15 minutes.
The wellKnown property points to the OIDC discovery endpoint (e.g., "https://accounts.google.com/.well-known/openid-configuration") that contains the provider's configuration in JSON format. NextAuth.js recommends using the wellKnown option instead of manually configuring individual endpoints if your provider is OpenID Connect (OIDC) compliant. You can still override scopes when using wellKnown.
NextAuth.js documentation shows support for empty scope strings (scope: '') for custom providers. However, there are known issues with certain built-in providers like Twitch, where providing an empty scope string throws an "id_token not present in TokenSet" error. Twitch allows developers to specify no scope for basic login, but this causes errors in NextAuth.js.
NextAuth.js handles URL encoding automatically when you use the authorization.params object format. Special characters in scope values (like colons, slashes, or dots in Azure AD scopes such as "api://backend_client_id/access_as_user") are properly encoded when passed through the params object rather than manually constructing the full URL string.
No, automatic account linking on sign in is disabled by default for security reasons. Normally, when you sign in with an OAuth provider and another account with the same email address already exists, the accounts are not linked automatically. However, if you sign in with one OAuth provider and while still signed in you sign in with another OAuth provider, NextAuth.js will link them to the same user (requires a database). You can implement custom account linking in the signIn callback.
Yes, you can set authorization to be a full URL with query parameters. For example: authorization: "https://example.com/oauth/authorization?scope=email". However, using the authorization: { params: { scope: "..." } } object format is more common and recommended.
The signIn() function accepts three parameters: provider, options, and authorizationParams. You can pass dynamic parameters in the third argument: signIn('auth0', { callbackUrl }, { scope: 'custom_scope' }). However, there are reported issues where passing authorizationParams in the signIn function doesn't work in some cases (particularly with Auth.js v5 beta), while passing them in the config file does work.
Security > CSRF Protection
23 questionsgetCsrfToken() from 'next-auth/react' returns the current CSRF token required to make POST requests. It can be called without parameters on the client.
The __Host- prefix requires the cookie to have secure: true (HTTPS only), path: '/', and no Domain attribute (host-only cookie), making it stricter than __Secure-.
If you specify custom options for a cookie, you must provide all the options for that cookie (name and all options properties like httpOnly, sameSite, path, secure).
The documentation states: 'Using this option is an advanced option and using it is not recommended as you may break authentication or introduce security flaws into your application.'
The endpoint is GET /api/auth/csrf which returns a JSON object containing the CSRF token.
NEXTAUTH_SECRET should be set to a random string used to hash tokens, sign/encrypt cookies and generate cryptographic keys in production.
The cookie value contains two parts separated by a pipe character: csrfToken|csrfTokenHash, where csrfTokenHash is SHA-256(csrfToken + secret).
A 'MissingCSRF' error is thrown when actions lack the double submit cookie essential for CSRF protection.
NextAuth.js generates CSRF tokens using randomBytes(32).toString('hex'), which creates 32 random bytes encoded as a 64-character hexadecimal string.
Import getCsrfToken from 'next-auth/react' and call it with the context object: await getCsrfToken(context) in getServerSideProps.
No, NextAuth only handles CSRF protection for /api/auth/[...nextauth] requests automatically. For custom API routes, you need to implement your own CSRF protection for client-server interaction.
The default cookie name is '__Host-next-auth.csrf-token' (or '__Host-authjs.csrf-token' in newer versions) when useSecureCookies is true (HTTPS environments).
NextAuth.js uses SHA-256 (createHash('sha256')) to hash the concatenation of the CSRF token and the application secret for validation.
useSecureCookies defaults to true for all site URLs that start with 'https://', and false for URLs that start with 'http://' (like http://localhost:3000).
Common causes include: missing CSRF token cookie, cookie configuration issues preventing storage, custom sign-in pages not properly passing the token, or race conditions when calling methods immediately after login.
httpOnly: true, sameSite: 'lax', path: '/', secure: (depends on useSecureCookies - true for HTTPS, false for HTTP)
Only POST requests to authentication endpoints require CSRF tokens. GET requests (like /api/auth/session, /api/auth/csrf, /api/auth/providers) do not require CSRF tokens.
NextAuth.js uses the 'double submit cookie method' with a signed HttpOnly, host-only cookie for CSRF protection on all authentication routes.
POST /api/auth/signin, POST /api/auth/signout, and POST /api/auth/callback/credentials (and other provider callbacks) all require CSRF tokens.
For OAuth 2.0 providers that support checks: ['state'], NextAuth checks the state parameter against a hash of the CSRF token, which MUST match for both the POST and GET calls during sign-in.
The CSRF token must be passed as a form variable named exactly 'csrfToken' (case-sensitive) in all POST submissions to any NextAuth API endpoint.
No, the CSRF token cookie has httpOnly: true by default, which prevents client-side JavaScript from reading the cookie value.
The default cookie name is 'next-auth.csrf-token' (or 'authjs.csrf-token' in newer versions) without any prefix when useSecureCookies is false (HTTP/development environments).
Next.js App Router Integration > Client Components
23 questionsuseSession accepts a required parameter (boolean) and an onUnauthenticated callback function. When required: true, it ensures a valid session, and onUnauthenticated() is called when the user is not authenticated (default behavior redirects to sign-in page).
No, the
By default, only URLs on the same origin as the site are allowed. The default redirect callback allows relative URLs starting with '/', and allows callback URLs where the URL's origin equals the baseUrl. Otherwise, it redirects to baseUrl.
getSession() returns Promise<null | Session>, where Session contains user object (with name, email, image) and expires (Date - the expiry of the session).
When redirect: false is used, signOut returns the validated URL that the user should have been redirected to, which you can use with Next.js's useRouter().push() to avoid page flicker.
The status enum maps to three possible session states: 'loading', 'authenticated', and 'unauthenticated'.
getCsrfToken() returns Promise<string | null> - the current Cross Site Request Forgery Token (CSRF Token) required to make POST requests (e.g. for signing in and signing out).
getProviders() returns Promise<Record<string, ClientSafeProvider> | null>, where ClientSafeProvider contains: id, name, type, signinUrl, and callbackUrl.
The refetchInterval prop defaults to 0, which means no session polling occurs by default.
Passing the session prop (from getServerSideProps or getServerSession) to SessionProvider avoids checking the session twice on pages that support both server and client side rendering, preventing flickering/loading state on first load.
In newer versions, useSession also returns an update function that can be used to update the session data in client components.
The main exports from next-auth/react are: useSession (hook), SessionProvider (component), signIn (function), signOut (function), getCsrfToken (function), getProviders (function), and getSession (function).
The default value for refetchWhenOffline is true, meaning session polling will keep trying even when the device has no internet access.
Client-side functions and hooks must be imported from 'next-auth/react' (not 'next-auth/client', which was the old path before v4).
The recommended approach in Next.js App Router is to call auth() in server components to get the session, then pass the session data as props to client components when needed, rather than using SessionProvider and useSession.
The default value of the redirect parameter is true, which means the user will be redirected automatically after sign-in.
The default value for refetchOnWindowFocus is true, meaning tabs/windows will be updated and initialize the components' state when they gain or lose focus.
Components using useSession must be wrapped in a
You must add the 'use client' directive at the top of component files when using hooks like useSession, signIn, or signOut from next-auth/react in Next.js App Router components, even though the library exports are marked with 'use client' internally.
The default value for the redirect parameter in signOut is true, meaning by default calling signOut() will cause a page reload and redirect the user.
When redirect: false is used, signIn returns a response object containing: error (string), status (HTTP status code), ok (boolean - true if signin was successful), and url (null if there was an error, otherwise the url the user should have been redirected to).
The default maxAge for NextAuth sessions is 30 days (2,592,000 seconds), configured as maxAge: 30 * 24 * 60 * 60.
API and Client Methods > signIn Method
23 questionsYes, the signIn() method ensures the user ends back on the page they started on after completing a sign in flow, by defaulting the callbackUrl to the page URL the sign-in is initiated from.
Yes, the Email provider requires a database and cannot be used without one. It is not possible to enable email sign in without using a database.
In v5, signIn is exported from the main NextAuth configuration along with auth, handlers, and signOut. It can be used in Server Actions with 'use server' directive and supports both server-side and client-side usage. The configuration is now in a file named auth.ts in the root of your repository.
Yes, the signIn() method will handle CSRF Tokens automatically when signing in with email.
By default, only URLs on the same URL as the site are allowed. The redirect callback allows relative callback URLs (starting with '/'), allows callback URLs on the same origin (when new URL(url).origin === baseUrl), and falls back to baseUrl otherwise. This prevents open redirect vulnerabilities.
The SignInOptions interface has two main defined properties: callbackUrl (optional string) which specifies to which URL the user will be redirected after signing in, and redirect (optional boolean) which controls whether to redirect after sign-in.
You can specify a different callbackUrl by specifying it as the second argument of signIn(). For example: signIn('google', { callbackUrl: '/dashboard' }). This works for all providers.
Call the signIn() method with the provider's id as the first argument. For example: signIn('google') will redirect directly to Google's authentication page.
The callbackUrl defaults to the page URL the sign-in is initiated from.
The error property is set to 'CredentialsSignin' when the authorize callback returned null in the Credentials provider.
When using the Email provider, a magic link is sent via email that the user can click on to sign in. By default, NextAuth redirects to /api/auth/verify-request after sending the magic link, but this can be turned off by setting the redirect property to false in the signIn method.
In v4, signIn is imported from 'next-auth/react' for client-side usage.
This response indicates that authentication failed. Despite status: 200 and ok: true (which indicate the HTTP request succeeded), the presence of the error property shows the sign-in failed. You must check for the error property to determine if authentication actually succeeded, not the HTTP status code.
When using redirect: false with credentials, useSession may return null for the session and false for loading. Only a hard refresh/redirect updates the session. This requires careful handling of the session state updates.
A user account will not be created until the first time they verify their email address. If an email address is already associated with an account, the user will be signed in to that account when they use the link in the email.
When redirect: false is set, signIn() returns a Promise that resolves to an object with these properties: error (string | undefined), status (HTTP status code), ok (boolean indicating if signin was successful), and url (null if there was an error, otherwise the url the user should have been redirected to).
You can pass additional parameters to the /authorize endpoint through the third argument of signIn() following the Authorization Request OIDC spec. For example: signIn('auth0', { callbackUrl }, { screen_hint: 'signup' }). You can also set these parameters through provider.authorizationParams.
By default, NextAuth normalizes the email address, treating values as case-insensitive and removing any secondary email address that was passed in as a comma-separated list.
signIn<P extends RedirectableProviderType | undefined = undefined>(provider?: LiteralUnion
No, users are NOT persisted in the database when using the Credentials provider. It can only be used if JSON Web Tokens are enabled for sessions.
By default, when calling the signIn() method with no arguments, you will be redirected to the NextAuth.js sign-in page.
In v5, by default, the user is redirected to the current page after signing in. You can override this behavior by setting the redirectTo option with a relative path.
The redirect option is only available for credentials and email providers. It does not work with OAuth providers like Google, GitHub, etc.
API and Client Methods > getSession / auth
23 questionsWhen refetchOnWindowFocus is set to true (the default) tabs/windows will be updated and initialize the components' state when they gain or lose focus.
In contrast to useSession, getServerSession only returns a session object when a user has logged in (only when authenticated cookies are present), otherwise it returns null.
The auth function in NextAuth v5 returns Promise<null | Session>, meaning it can return either a valid Session object or null.
Yes, the methods getSession() and getToken() both return an object if a session is valid and null if a session is invalid or has expired.
In v5, you should use auth() instead of getServerSession, getSession, withAuth, getToken, and useSession.
getServerSession can drastically reduce response time when used over getSession on server-side, due to avoiding an extra fetch to an API Route.
The useSession() hook returns { data: session, status, update }, where update is a method that can be called to refresh or modify the session.
When called, getSession() sends a request to /api/auth/session and returns a promise with a session object, or null if no session exists.
The default behavior is to redirect the user to the sign-in page, from where - after a successful login - they will be sent back to the page they started on.
Yes, getServerSession will correctly update the cookie expiry time and update the session content if callbacks.jwt or callbacks.session changed something.
The update() method is available in NextAuth v4.21.1. Prior to this version, in v4.20.1 there was no update method.
When refetchInterval is set to 0 (the default) there will be no session polling.
The default session object contains: user with properties name, email, and image (all strings), and expires - a Date representing the expiry of the session.
The default maxAge is 30 days (30 * 24 * 60 * 60 seconds) for both JWT tokens and session cookies.
The status property is an enum mapping to three possible session states: "loading" | "authenticated" | "unauthenticated".
The updateAge setting throttles how frequently NextAuth writes to the database to extend a session, helping to limit write operations. Setting it to 0 will always update the database. This option is ignored if using JSON Web Tokens (JWT strategy).
Yes, Version 4 makes using the SessionProvider mandatory, meaning you will have to wrap any part of your application using useSession in this provider.
getServerSession requires three parameters: req (the request object), res (the response object), and authOptions (your NextAuth configuration object).
The useSession() hook returns an object containing two values: data and status.
The default value for updateAge when using the database strategy is 24 hours (24 * 60 * 60 seconds = 86,400 seconds).
When the update() method is called and you're using strategy: "jwt", the JWT callback is triggered with trigger: "update".
The NextAuth.js middleware only supports the "jwt" session strategy when running in Edge runtime environments.
getSession() should be called client side only to return the current active session. On the server side, getServerSession is recommended instead.
Credentials Authentication > Credentials Provider Setup
23 questionsimport CredentialsProvider from "next-auth/providers/credentials"
- 'credentials' - the user-submitted credentials (username, password, etc.), and 2) 'req' - the request object (which can be used to gather additional parameters like the request IP address).
The user will be sent to the error page with the error message as a query parameter.
A user object - any object returned will be saved in the 'user' property of the JWT. Common properties include 'id', 'name', and 'email', but you can return any custom properties.
id, name, and email - though you have full control over what properties you return. The example in the documentation shows: { id: "1", name: "J Smith", email: "[email protected]" }
- 'credentials' - an object defining the input fields to be submitted, and 2) 'authorize' - an async function that handles authentication logic and returns either a user object or null.
Encrypted (JWE) by default - tokens are not signed (JWS) but are encrypted, and it is recommended to keep this behavior.
"database" - when you use an adapter, the default session strategy automatically changes from "jwt" to "database".
Any HTML attribute can be passed to the tag through the credentials object, including 'label', 'type', 'placeholder', and standard HTML input attributes.
To discourage use of passwords due to the inherent security risks associated with them and the additional complexity associated with supporting usernames and passwords. NextAuth encourages OAuth providers or passwordless authentication instead.
You must pass a csrfToken from /api/auth/csrf in a POST request to /api/auth/callback/credentials. The CSRF token must be included as a form variable named 'csrfToken'.
JSON Web Tokens (JWT) must be enabled for sessions. Users authenticated with the Credentials provider are not persisted in the database, so the Credentials provider can only be used if JWT sessions are enabled.
redirect: false - pass this in the second parameter object to signIn(). This is only available for credentials and email providers and allows you to handle the sign-in response on the same page.
null - returning null will trigger an error advising the user to check their details and generates a 'CredentialsSignin' error.
/api/auth/callback/credentials - this is the POST endpoint where credentials and the CSRF token must be submitted.
No - if you want to use your own custom sign-in page, you do not need to pass email and password keys to the credentials object. The credentials object is primarily used to generate the auto-generated sign-in form.
The "double submit cookie method" - it uses a signed HttpOnly, host-only cookie for CSRF protection on all authentication routes.
"CredentialsSignin" - this is the default NextAuth error code indicating that the authorize callback returned null in the Credentials provider.
A response object containing: 'error' (error code if authentication fails), 'status' (HTTP status code), 'ok' (boolean indicating success), and 'url' (redirect URL on successful authentication).
The HTML input type for the form field (e.g., "text", "password", "email"). This is used when generating the auto-generated sign-in form.
Specify a unique 'id' property for each CredentialsProvider (e.g., 'domain-login' and 'intranet-credentials'). When calling signIn(), use the unique id instead of 'credentials'.
"Credentials" - this is the name displayed on the sign in form (e.g. "Sign in with...").
No, by default. The Credentials provider always creates a JWT session and does not support the database strategy flag. Combining Credentials authentication with database sessions requires custom workarounds involving JWT callbacks.
Email Authentication > Custom Email Templates
22 questionsThe default email subject line is "Sign in to ${host}", where ${host} is the domain name extracted from the URL (e.g., "Sign in to example.com").
Yes, you can integrate React Email with NextAuth by using the custom sendVerificationRequest function. React Email allows you to build email templates using React components, and you can render them within your custom sendVerificationRequest implementation to send through services like Resend.
No, NextAuth.js does not include nodemailer as a dependency. You must install it separately using npm install nodemailer, pnpm add nodemailer, yarn add nodemailer, or bun add nodemailer if you want to use the Email/Nodemailer provider.
By default, NextAuth normalizes email addresses by converting them to lowercase and trimming whitespace: identifier.toLowerCase().trim(). The email is split on '@' to separate local and domain parts. This treats email addresses as case-insensitive.
The default brandColor is "#346df1" (blue). This is used for the buttonBackground and buttonBorder in the email template.
The from field is required and must be explicitly specified in your configuration. It does not have a built-in default value. It accepts either a simple email address (e.g., '[email protected]') or an email with display name (e.g., 'Your name [email protected]').
The sendVerificationRequest function receives an object with the following parameters: expires (Date), identifier (string - the user's email), provider (EmailConfig), request (Request object), theme (Theme object), token (string - verification token), and url (string - the magic link callback URL with token).
The source code for the default HTML and text template functions is located in the NextAuth repository at packages/next-auth/src/providers/email.ts. The official documentation provides the complete source code that you can copy and modify.
The theme object passed to sendVerificationRequest includes: brandColor (hex color for buttons), buttonText (hex color for button text), and potentially other properties. In the NextAuth configuration, you can also set colorScheme ("auto" | "dark" | "light") and logo (absolute URL to image), though these primarily affect built-in pages rather than email templates.
The default maxAge value for the email provider is 24 hours (24 * 60 * 60 seconds). This determines how long email login links remain valid.
The SMTP connection string format is: smtp://username:[email protected]:587. You set this as the EMAIL_SERVER environment variable and pass it to the provider's server property.
No, the email provider requires a database to be configured and cannot be used without one. A database adapter is mandatory for storing email verification tokens.
No, the sendVerificationRequest function does not require a specific return value. It should be async and is used primarily for its side effects (sending an email). Errors should be thrown if the email fails to send, rather than returning error values.
Yes, you can apply your own normalization via the normalizeIdentifier method on the EmailProvider configuration. This allows you to customize what your system considers a valid email address.
The server object requires: host (EMAIL_SERVER_HOST), port (EMAIL_SERVER_PORT), and auth containing user (EMAIL_SERVER_USER) and pass (EMAIL_SERVER_PASSWORD). Additionally, a from field is required at the provider level.
The default colors are: background: "#f9f9f9", text: "#444", mainBackground: "#fff", buttonBackground: uses brandColor ("#346df1"), buttonBorder: uses brandColor ("#346df1"), buttonText: "#fff".
The email provider requires a VerificationToken table with three fields: identifier (String - the email address), token (String, unique - the verification token), and expires (DateTime - token expiration), plus a unique constraint on the combination of [identifier, token]. Note: there is no id field required in NextAuth v4+.
In NextAuth v4 and earlier, the built-in EmailProvider has a default id of "email". In NextAuth v5 (Auth.js), the Nodemailer provider has an id of "nodemailer".
You fully customize the sign-in email by passing a custom function as the sendVerificationRequest option to EmailProvider(). This function receives parameters including identifier, url, provider, theme, token, expires, and request, allowing you to send customized emails through any email service.
Yes, you can define a custom generateVerificationToken method in your email provider options. The default implementation uses crypto.randomBytes(32).toString('hex'), but you can override it to return any custom token string, such as crypto.randomUUID().
OAuth Providers > GitHub Provider
20 questionsYou must add the "user" scope to your configuration. The default scope only provides read-only access ("read:user user:email"), so if you need write access, you have to explicitly configure it.
For Next.js, the callback URL format is [origin]/api/auth/callback/github. For example: http://localhost:3000/api/auth/callback/github for local development or https://example.com/api/auth/callback/github for production.
AUTH_{PROVIDER}_{ID|SECRET}, where PROVIDER is the uppercase snake case version of the provider's id. For example, AUTH_GITHUB_ID and AUTH_GITHUB_SECRET for the GitHub provider.
Next.js uses /api/auth/callback/github while SvelteKit and Qwik use /auth/callback/github (without the /api path parameter).
Setting allowDangerousEmailAccountLinking: true allows automatic account linking when a user signs in with an OAuth provider and another account with the same email address already exists. By default, automatic account linking is disabled for security reasons.
The default scope is "read:user user:email". This provides read-only access to profile information and email addresses. Previously it was "user" which granted read/write access, but this was changed to follow the principle of least privilege.
The default profile callback returns: id (user's GitHub ID), name (display name or login), email (primary email address), and image (avatar URL).
OAuth 2.0. By default, Auth.js assumes that the GitHub provider is based on the OAuth 2 specification.
You can simply add GitHub to your providers array without calling it as a function: providers: [GitHub]. The AUTH_GITHUB_ID and AUTH_GITHUB_SECRET environment variables will be automatically detected.
GitHub only allows one callback URL per Client ID / Client Secret. You need to create separate OAuth applications for different environments (development, staging, production).
No, it's no longer required in v5. Previously this field was required to be added to the account table, but this is no longer the case and you can remove it if you are not using it. If you do use it, make sure to return it via the new account() callback.
AUTH_GITHUB_ID and AUTH_GITHUB_SECRET. If you name your environment variables using this syntax, they will be auto-detected and you won't have to explicitly pass them into your provider's configuration.
The GitHub provider automatically fetches private emails from https://api.github.com/user/emails when the profile email is not public. This requires the user:email scope and the "Email addresses" account permission set to read-only.
GitHub returns a field called refresh_token_expires_in (a number). Remember to add this field to your database schema if you are using an Adapter.
The "Email addresses" account permission must be set to read-only in order to access private email addresses on GitHub when creating a GitHub App.
You can provide a custom profile() callback function in the GitHub provider configuration. For example: GitHubProvider({ profile(profile) { return { id: profile.id.toString(), name: profile.name || profile.login, email: profile.email, image: profile.avatar_url } } })
Account Linking
20 questionsSet allowDangerousEmailAccountLinking: true for both providers, but inside the signIn callback check if the account parameter is the provider you don't fully trust, and based on the profile parameter check if email is verified, then return true/false accordingly.
Events are asynchronous functions that do not return a response. They are useful for audit logging. The execution of your authentication API will be blocked by an await on your event handler.
It can be exploited by bad actors to hijack accounts by creating an OAuth account associated with the email address of another user. When an email address is associated with an OAuth account it does not necessarily mean that it has been verified.
The signIn callback can return false to display a default error message, or return a URL string to redirect to (like '/unauthorized').
Email address verification is not part of the OAuth specification and varies between providers - some do not verify first, some do verify first, others return metadata indicating the verification status.
Yes. The option can accept a function that receives the profile and returns a boolean based on verification status, allowing you to check if the email is verified before linking accounts.
userId (string representing the id of the user this account belongs to), type (provider's type), provider (provider name for OAuth/OIDC providers), and providerAccountId (for oauth/oidc it's the OAuth account's id, returned from the profile() callback).
/api/auth/callback/[provider] → /api/auth/error?error=OAuthAccountNotLinked → /api/auth/signin?error=OAuthAccountNotLinked
The new provider account will be linked automatically to the same authenticated user, regardless of the primary emails for each provider accounts. This flow is not affected by the allowDangerousEmailAccountLinking option.
It is configured on a per-provider basis. You can enable it for specific trusted providers (like Google, GitHub) while keeping it disabled for others, giving granular control over which providers can automatically link accounts based on email addresses.
false. Automatic account linking on sign in is disabled by default.
Automatic account linking on sign in is not secure between arbitrary providers - with the exception of allowing users to sign in via an email addresses as a fallback (as they must verify their email address as part of the flow).
By configuring the pages option in NextAuth: pages: { error: '/auth/error' }. The error code is passed in the query string as ?error=.
No. Users authenticated with the Credentials provider are not persisted in the database. The Credentials provider can only be used if JSON Web Tokens are enabled for sessions.
It was originally a feature in v1.x but has not been present since v2.0, and is planned to return in a future release.
It may be desirable to allow automatic account linking if you trust that the provider involved has securely verified the email address associated with the account. For well-known providers like GitHub, Google or Microsoft, enabling allowDangerousEmailAccountLinking=true is not considered as bad practice.
No. The unlinkAccount method is currently not invoked yet by Auth.js, though most adapters have it implemented.
The linkAccount event. It is sent when an account in a given provider is linked to a user in the user database, for example when a user signs up with Twitter or when an existing user links their Google account.
OAuthAccountNotLinked error. This occurs if the email on the account is already linked, but not with this OAuth account.
A unique constraint on the combination of provider and providerAccountId fields.
Configuration Options > Debug and Logging
20 questionsEnabling the debug option in production can lead to sensitive information being saved in your logs. This includes potentially exposing identity provider secrets, user data, and other confidential authentication details.
The recommended pattern is to set debug: process.env.NODE_ENV !== "production", which automatically enables debug mode in development and disables it in production. This can be safely committed to version control without needing environment-specific changes.
NextAuth provides three logger levels: error, warn, and debug. Each can be customized independently.
If the debug level is defined by the user in the logger configuration, it will be called regardless of the debug: false option. The custom debug logger function overrides the debug option setting.
When debug is set to true, it uses console methods to log many details about the authentication process, including requests, responses, errors, and database requests and responses.
The debug option accepts a boolean value - either true to enable debug logging or false to disable it.
Undefined logger levels will use the built-in logger. This allows partial customization where you can override only specific logging levels while the rest fall back to NextAuth's default console-based logging.
No, there is no NEXTAUTH_DEBUG environment variable in NextAuth.js/Auth.js. Debug mode is controlled through the debug configuration option in the NextAuth configuration object, not through environment variables.
For production logging, it's recommended to use the logger option with a custom implementation that includes proper sanitization of potentially sensitive user information, rather than using the debug: true option.
The debug function signature is debug(code, metadata) where code is a string identifier and metadata contains additional debug information.
NextAuth's built-in logger defaults to using the console object with its standard methods: console.error for errors, console.warn for warnings, and console.debug for debug messages.
The code parameter for error and warn methods are explained in the Warnings page (https://next-auth.js.org/warnings) and Errors page (https://next-auth.js.org/errors) respectively in the NextAuth documentation.
The metadata parameter is usually an object containing error details or provider-related information. It can contain Error instances or provider data, which may include sensitive information that should be sanitized before logging.
When logger options are set, the debug option is ignored. The custom logger takes precedence over the debug configuration setting.
The warn function signature is warn(code) which takes only a code parameter (typically a string identifier).
The debug option is false by default (disabled). You need to explicitly set debug: true to enable debug messages for authentication and database operations.
The information disclosure vulnerability was patched in next-auth v4.10.2 and v3.29.9. These versions moved provider information logs to the debug level and added warnings for production debug usage.
The error function signature is error(code, metadata) where code is typically a string identifier and metadata is usually an object containing error details.
The fix moved the log for provider information to the debug level, preventing sensitive information like identity provider secrets from being logged at higher levels. A warning was also added for having debug: true in production.
The logger option is typed as Partial<LoggerInstance>, which means all logger methods (error, warn, debug) are optional and you can override only the ones you need.
Credentials Authentication > Custom Validation
20 questionsYes, you should check if credentials exist before proceeding. Best practice is: if (!credentials?.email || !credentials?.password) { return null; }. This prevents 'Cannot read properties of undefined' errors. It's recommended to validate inputs using tools like Zod before they reach the authorize callback.
If you return null from the authorize function, an error will be displayed advising the user to check their details. This results in a CredentialsSignin error with HTTP status 401.
NextAuth implements CSRF protection using the 'double submit cookie method' with a signed HttpOnly, host-only cookie. The CSRF token from /api/auth/csrf must be passed as a form variable named csrfToken in all POST submissions to any API endpoint, including /api/auth/callback/credentials.
The Credentials provider can only be used if JSON Web Tokens are enabled for sessions. Users authenticated with the Credentials provider are not persisted in the database.
You define fields in the credentials object, which is used to generate a form on the sign-in page. Each field can have properties like label, type, and placeholder. You can pass any HTML attribute to the tag. Example: credentials: { username: { label: 'Username', type: 'text', placeholder: 'jsmith' }, password: { label: 'Password', type: 'password' } }
The authorize() function must return a user object (e.g., { id: '1', name: 'J Smith', email: '[email protected]' }). If you return an object it will be persisted to the JSON Web Token and the user will be signed in, unless a custom signIn() callback is configured that subsequently rejects it.
The session.user object by default only contains three properties: name, email, and image. Custom properties added to the user object are not automatically persisted to the session and require callbacks (jwt and session callbacks) to explicitly pass them through.
NextAuth deliberately restricts error information with this recommendation: 'We don't recommend providing information about which part of the credentials were wrong, as it might be abused by malicious hackers.' The default CredentialsSignin error is intentionally generic for security purposes.
Yes, the authorize function can be declared as async and use await for asynchronous operations like database queries or API calls. It can also explicitly return Promise.resolve({}) for successful authentication or Promise.resolve(false) for failed authentication.
When authorize() returns null, the HTTP status is 401. However, when credentials are valid and authorize() returns a user object, the status is 200. Note: with redirect: false and certain configurations, errors may incorrectly return status 200, which is a known issue.
If you are using a database adapter, then Database Sessions are enabled by default and you need to explicitly enable JWT Sessions to use the Credentials Provider by setting session: { strategy: 'jwt' } in your NextAuth configuration.
Any object returned from the authorize function will be saved in the user property of the JWT. The user object is then passed through the jwt callback system and eventually to the session.
In NextAuth v5, thrown errors from the authorize callback typically manifest as a 'CallbackRouteError' regardless of whether authorize returns null or throws an error. This is why the recommendation is to return null for invalid credentials instead of throwing errors.
Yes, you can pass any HTML attribute to the tag through the credentials object configuration. Common attributes include label, type, and placeholder, but you can add any valid HTML input attributes.
The authorize() function receives two parameters: credentials (the first parameter containing the credentials submitted by the user) and req (the second parameter which is the request object). The signature is: async authorize(credentials, req)
When using the Credentials Provider, the user object in the signIn() callback is the response returned from the authorize callback, and the profile object is the raw body of the HTTP POST submission. The signIn() callback can return true to allow sign in, false to display a default error message, or a URL to redirect to. This allows additional control even after successful credential validation.
When the authorize function returns null, NextAuth returns a 'CredentialsSignin' error code via an error searchParam. The error message states: 'The authorize callback returned null in the Credentials provider.'
In NextAuth v5, you can throw a custom error by extending the CredentialsSignin class. Example: class InvalidLoginError extends CredentialsSignin { code = 'Invalid identifier or password' }. Then throw this custom error in the authorize function.
If you throw an Error, the user will be sent to the error page with the error message as a query parameter. The thrown error message becomes available as a query parameter.
When redirect: false is set in the signIn function, it skips the redirect and error searchParam. Instead, signIn returns an object containing error (error codes), status (HTTP status code), ok (boolean indicating if signin was successful), and url properties that can be used to handle the error in your form component.
Callbacks > signIn Callback
20 questionsThe isNewUser parameter is mentioned in documentation as being passed to callbacks, but current NextAuth v4 signIn callback signature does not include isNewUser - it is primarily available in the jwt callback.
- Return true to allow the user to sign in, 2) Return false to display a default error message, 3) Return a URL string (e.g., '/unauthorized') to redirect to that URL
Yes, the signIn callback is an asynchronous function and fully supports async/await and Promises.
By default, NextAuth requires the URL to be an absolute URL at the same hostname, or a relative URL starting with a slash. If it does not match, it will redirect to the homepage.
These parameters are only passed the first time this callback is called on a new session, after the user signs in. On subsequent calls, they will not be available.
When using NextAuth.js with a database, the User object will be either a user object from the database (including the User ID) if the user has signed in before or a simpler prototype user object.
When using the Credentials Provider, the profile object is the raw body of the HTTP POST submission.
The signIn() callback is triggered twice: once when the user makes a Verification Request (before they are sent an email) and again after they activate the link in the sign in email.
When using NextAuth.js without a database, the user object will always be a prototype user object, with information extracted from the profile.
On the first call during email sign in, the email object will include a property verificationRequest: true to indicate it is being triggered in the verification request flow.
Yes, you can check for the verificationRequest property to avoid sending emails to addresses or domains on a blocklist (or to only explicitly generate them for email addresses in an allow list).
On sign in, the raw profile object returned by the provider is passed as a parameter. It is not available on subsequent calls.
Redirects returned by this callback cancel the authentication flow and should only redirect to error pages that explain why users aren't allowed to sign in.
async signIn({ user, account, profile, email, credentials }) { }
When using the Credentials Provider, the user object is the response returned from the authorize callback.
When the signIn callback returns false, users are redirected to the error page with error=AccessDenied in the query string.
The OAuthAccountNotLinked error occurs if the email on the account is already linked, but not with this OAuth account. This happens when trying to access your account through different social accounts that share the same email address.
To redirect to a page after a successful sign in, use the callbackUrl option or the redirect callback instead of the signIn callback.
When no signIn callback is defined, the default behavior is to allow all sign-ins (effectively returning true for all authentication attempts).
Callbacks > jwt Callback
20 questionsThe default session strategy is 'jwt'. However, if you use an adapter, NextAuth.js defaults it to 'database' instead.
These parameters are only passed the first time the jwt callback is called on a new session, after the user signs in. In subsequent calls, only the token parameter will be available.
There is typically a limit of around 4096 bytes (4kb) per cookie. If you want to support most browsers, do not exceed 4096 bytes per cookie.
A random string (NEXTAUTH_SECRET) is used to hash tokens, sign/encrypt cookies and generate cryptographic keys. The secret doesn't directly encrypt the JWT; instead, the encryption key is derived using HKDF (HMAC-based Extract-and-Expand Key Derivation Function). If you set NEXTAUTH_SECRET as an environment variable, you don't have to define this option explicitly.
The default maxAge is 30 days, which equals 2,592,000 seconds (60 * 60 * 24 * 30).
By default, tokens are not signed (JWS) but are encrypted (JWE). It's recommended to keep this behavior.
When using strategy: 'jwt', the update() method will trigger a jwt callback with the trigger: 'update' option. You can use this to update the session object on the server.
The data passed in the update() method is passed to the session argument of the JWT callback. You can access it via the session parameter, e.g., if (trigger === 'update' && session?.name) { token.name = session.name }
The jwt callback must always return the token argument. The returned value will be encrypted and stored in a cookie.
You should import from 'next-auth/jwt' (not 'next-auth'). Use module augmentation with: declare module 'next-auth/jwt' { interface JWT { /* custom properties */ } }
When using JSON Web Tokens, the jwt() callback is invoked before the session() callback, so anything you add to the JSON Web Token will be immediately available in the session callback.
When using the update() method with JWT strategy, the jwt callback receives trigger: 'update' in its parameters.
The jwt callback receives: { token, user, account, profile, isNewUser }. However, user, account, profile and isNewUser are only passed the first time this callback is called on a new session, after the user signs in. In subsequent calls, only token will be available.
The JWT returned by the jwt callback is encrypted and stored in a server-readable-only cookie (HttpOnly), so data in the JWT is not accessible to third party JavaScript running on your site.
No. The jwt callback is not invoked when you persist sessions in a database. It only runs when using JWT session strategy.
The jwt callback is invoked whenever a JSON Web Token is created (at sign in) or updated (whenever a session is accessed in the client). Specifically, requests to /api/auth/signin, /api/auth/session and calls to getSession(), getServerSession(), useSession() will invoke this function, but only if you are using a JWT session.
NextAuth.js uses encrypted JSON Web Tokens (JWE) by default with the A256CBC-HS512 algorithm (some versions may use A256GCM).
Since v4, NextAuth.js implements cookie chunking so that cookies over the 4kb limit get split and reassembled upon parsing. When a cookie exceeds the browser limit, NextAuth.js chunks it and saves it in multiple cookies with a .{number} suffix, then stitches them back together when reading on the backend.
The jwt callback returns Awaitable<null | JWT>, meaning it can return either a JWT object, null, or a Promise that resolves to either.
No. NextAuth.js uses encryption (JWE) rather than just signing (JWS), and employs key derivation functions (HKDF). Standard JWT libraries cannot decrypt NextAuth tokens.
Callbacks > session Callback
20 questionsNo, the account, profile, and isNewUser parameters are only available in the jwt callback, not the session callback. The session callback only receives session, user (database strategy), token (JWT strategy), trigger, and newSession parameters.
The session callback has a return type of Awaitable<Session | DefaultSession>, which means it can return a Session type, a DefaultSession type, or a Promise that resolves to either of these types.
You need to intersect your custom properties with DefaultSession["user"] like this: user: { customField: string } & DefaultSession["user"] to keep the default properties (name, email, image).
Yes, callbacks are asynchronous functions in NextAuth.js and the session callback should always be declared with the async keyword.
You must declare module "next-auth" with an interface Session inside it to extend the session type using TypeScript module augmentation.
With the database session strategy, the session callback receives the user parameter, which is the user object from the database, and there is no token parameter.
When trigger equals "update", it indicates the session callback was triggered by an update operation (via the update() method), and a newSession parameter will contain the new data passed from the update() call.
Requests to /api/auth/signin, /api/auth/session and calls to getSession(), getServerSession(), and useSession() will invoke the jwt callback when using JWT sessions.
The expires value is rotated - whenever the session is retrieved from the REST API, this value will be updated to avoid session expiry.
The jwt() callback is invoked before the session() callback. The jwt callback is called first, followed by the session callback.
If you use an adapter, the default session strategy is "database" instead of JWT. However, you can still force a JWT session by explicitly defining strategy: "jwt".
Yes, you must always return a session object from the session callback. The callback cannot return null or undefined under normal circumstances.
The session callback is called whenever a session is checked: getSession(), useSession(), and requests to the /api/auth/session endpoint all trigger the session callback.
By default, NextAuth sessions expose only three user properties for security: name (string), email (string), and image (string).
The default maxAge value is 30 days, which equals 2,592,000 seconds (calculated as 30 × 24 × 60 × 60).
The token.sub property contains the unique identifier of the user. You can use session.user.id = token.sub in the session callback to forward the user ID to the session.
The recommended method is getServerSession() because it's a server-side only function that doesn't trigger unnecessary fetch calls over the internet, increasing performance significantly.
When using JSON Web Tokens for sessions, the session callback receives the token parameter (JWT payload) instead of the user object.
No, when using auth(), the session() callback is ignored. The auth() method will expose anything returned from the jwt() callback or, if using a "database" strategy, from the User object directly.
The return value will be exposed to the client, so be careful what you return here. By default, only a subset of the token is returned for increased security.
Custom Pages > Verify Request Page
19 questionsNo, setting redirect: false does not prevent the redirect to the verifyRequest page in all versions. This was a known issue in earlier versions (around v3.13.0), but appears to have been addressed in version 4 and later.
Yes, when you add an entry to the pages object in your NextAuth configuration for verifyRequest, NextAuth automatically performs the redirect for you.
NextAuth adds provider (e.g., 'email' or 'nodemailer') and type (e.g., 'email') query parameters to the URL.
The verifyRequest page is used for check email message, typically shown when using email-based authentication to inform users to check their email for a verification link.
It refers to a page file at pages/auth/verify-request.js (or .jsx/.tsx for TypeScript/JSX).
The verifyRequest property accepts a string value representing the URL path (e.g., '/auth/verify-request').
The available page options are: signIn, signOut, error, verifyRequest, and newUser.
The default URL is /api/auth/verify-request with query parameters, for example: /api/auth/verify-request?provider=email&type=email
Yes, pages specified in the pages configuration will override the corresponding built-in page.
The verification page should be created at app/auth/verify-request/page.tsx in the App Router.
The pages option is optional and has type Partial<PagesOptions>, meaning all properties including verifyRequest are optional.
The property name is verifyRequest and it is configured within the pages option, for example: pages: { verifyRequest: '/auth/verify-request' }
No, the verifyRequest page does not receive the email address as a query parameter or prop, making it impossible to display which email the magic link was sent to without custom implementation.
By default, the built-in pages will follow the system theme, utilizing the prefer-color-scheme Media Query.
The verifyRequest page is shown after a user submits their email address when using the Email provider for passwordless authentication, before they click the magic link sent to their inbox.
The Email provider (used for passwordless/magic link authentication) triggers the verifyRequest page to be shown.
You can define theme.logo (a URL to a logo rendered above the main card), theme.brandColor (affects the accent color), and theme.colorScheme ('light', 'dark', or 'auto').
Query parameters were fixed to carry over to custom verifyRequest pages through pull request #12166 which was merged in November 2024.
The verifyRequest page shows a confirmation message after email authentication telling users to check their inbox, while the newUser page redirects brand new users on their first successful sign-in (useful for onboarding flows).
API and Client Methods > getCsrfToken
18 questionsYou must pass a csrfToken from /api/auth/csrf in a POST request to /api/auth/callback/credentials along with your credential fields.
Pass the request object: export default async (req, res) => { const csrfToken = await getCsrfToken({ req }); /* ... */ res.end(); }
You must pass the GetServerSidePropsContext object. Example: await getCsrfToken(context) where context is the parameter from getServerSideProps.
You likely only need to use getCsrfToken() if you are NOT using the built-in signIn() and signOut() methods. The built-in methods handle CSRF tokens automatically.
The CSRF token must be passed as a form variable named 'csrfToken' (exact spelling, camelCase) in all POST submissions to any NextAuth API endpoint.
In v3, it was called csrfToken. It was renamed to getCsrfToken in v4.
GET /api/auth/csrf returns a JSON object with the format: { "csrfToken": "your-csrf-token-value" }
NextAuth.js uses the 'double submit cookie method' with a signed HttpOnly, host-only cookie. CSRF protection is present on all authentication routes.
The csrfToken cookie is configured with: httpOnly: true, sameSite: 'lax', path: '/', and secure: true (when using HTTPS).
If the request misses the __Host-next-auth.csrf-token cookie or the csrfToken body property, the NextAuth server will redirect to /api/auth/signin?csrf=true.
No, there is no exposed API that enables getting a CSRF token in Server Components (Next.js App Router). In the latest version of next-auth, getCsrfToken is client-only.
__Host-next-auth.csrf-token. The __Host- prefix is a security feature that requires the secure flag, no domain specification, and path must be '/'.
Yes. Client Side: Yes · Server Side: Yes. However, when using it server-side, you must pass the context parameter, while client-side calls require no arguments.
getCsrfToken() returns Promise
You must submit both the email address field and csrfToken from /api/auth/csrf in a POST request to /api/auth/signin/email.
Import from 'next-auth/react': import { getCsrfToken } from 'next-auth/react'
async function myFunction() { const csrfToken = await getCsrfToken(); /* use token */ }
Callbacks > redirect Callback
17 questionsThe redirect callback returns Awaitable<string>, meaning it can return either a string directly or a Promise that resolves to a string representing the URL to redirect to.
When allowing external domains, implement a whitelist of allowed domains and validate that the URL starts with one of the whitelisted domains before returning it. Always return baseUrl as the fallback for safety. Example: check if allowedDomains.some(domain => url.startsWith(domain)) before allowing the redirect.
No, the NEXTAUTH_URL/AUTH_URL is not strictly necessary anymore in most v5 environments. NextAuth will auto-detect the host based on the request headers, particularly when the AUTH_TRUST_HOST environment variable is set to true or trustHost is configured.
The default redirect callback returns baseUrl as the fallback, effectively redirecting to the homepage if the URL doesn't meet the security criteria.
If the URL starts with "/", the default redirect callback returns ${baseUrl}${url}, concatenating the baseUrl with the relative URL.
In v3, the redirect callback used positional parameters: redirect(url, baseUrl). In v4, it changed to use named parameters with object destructuring: redirect({ url, baseUrl }).
By default, only URLs on the same origin as the site are allowed. The URL must be either an absolute URL at the same hostname or a relative URL starting with a slash.
Yes, the redirect callback may be invoked more than once in the same flow.
The redirect callback receives two parameters destructured from an object: url (the URL being redirected to) and baseUrl (the base URL/origin of your application).
If new URL(url).origin === baseUrl, the default redirect callback returns the url as-is, allowing callback URLs on the same origin.
If the URL doesn't start with "/" and its origin doesn't match the baseUrl, the default redirect callback will redirect to the homepage (returns baseUrl).
NextAuth v3 users before version 3.29.2 and next-auth v4 users before version 4.3.2 were impacted by an open redirect vulnerability. Upgrading to 3.29.2 or 4.3.2 patches this vulnerability.
The baseUrl is typically derived from the NEXTAUTH_URL environment variable or automatically detected from the request's origin. It represents the canonical origin of your application.
The redirect callback is called during redirects to callback URLs on signin/signout. To redirect to a specific page after successful sign in, you should use the callbackUrl option when calling signIn() rather than implementing complex logic in the redirect callback.
The url parameter can come from several sources: query parameters (e.g., when redirecting to login from protected routes), explicitly set in the signIn() method's callbackUrl option, or from cookies that NextAuth maintains.
callbacks: {
async redirect({ url, baseUrl }) {
// Allows relative callback URLs
if (url.startsWith("/")) return `${baseUrl}${url}`
// Allows callback URLs on the same origin
else if (new URL(url).origin === baseUrl) return url
return baseUrl
}
}
The redirect callback is called anytime the user is redirected to a callback URL, such as during signin or signout operations.
Custom Pages > Sign Out Page
16 questionstheme.colorScheme, theme.logo, theme.brandColor, and theme.buttonText are all correctly applied on the default signout page (though brandColor and buttonText have known issues on the signin page).
The specified custom page will override the corresponding built-in page. You must ensure the page actually exists at that path.
A built-in sign-out confirmation page (the 'Are you sure you want to sign out?' page)
It defines a hex color value that affects the accent color of built-in pages including signin, signout, error, and verify-request
pages.signOut - it accepts a string value representing the path where your custom sign-out page is located, e.g., pages: { signOut: '/auth/signout' }
The page will not reload. The session will be deleted and the useSession hook is notified, so any indication about the user will be shown as logged out automatically.
"light", "dark", or "auto" (default) - controls whether built-in pages including signout follow a light theme, dark theme, or the preferred system theme
/api/auth/signout - this is the automatically created endpoint that displays a confirmation page before signing the user out
signOut({ callbackUrl: 'http://localhost:3000/foo' }) or signOut({ callbackUrl: '/custom-page' })
If the URL starts with "/", it returns ${baseUrl}${url}. If the URL's origin equals baseUrl, it returns the url. Otherwise, it returns baseUrl.
POST - signing out is a POST submission to prevent malicious links from triggering sign out without user consent. The POST submission requires a CSRF token from /api/auth/csrf.
Custom pages must be in a folder outside /pages/api which is reserved for API code. The recommended convention is /pages/auth/ (e.g., pages/auth/signout.js)
No - you likely only need to use CSRF tokens directly if you are NOT using the built-in signIn() and signOut() methods, which handle the CSRF token automatically.
A CSRF token from /api/auth/csrf passed as a form variable named 'csrfToken'
The URL must be an absolute URL at the same hostname or a relative URL starting with a slash. If it doesn't match, it will redirect to the homepage. The URL must be considered valid by the redirect callback handler.
It defines an absolute URL to a logo image which will be rendered above the main card on built-in pages including signin, signout, error, and verify-request
Credentials Authentication > Limitations
15 questionsThe functionality provided for credentials based authentication is intentionally limited to discourage use of passwords due to the inherent security risks associated with them and the additional complexity associated with supporting usernames and passwords.
You must explicitly set session strategy to 'jwt' in your configuration. When an adapter is present, NextAuth defaults to database sessions, but the Credentials provider requires JWT sessions, so you need to override this with: session: { strategy: 'jwt' }
The arguments user, account, profile and isNewUser are only passed the first time the JWT callback is called on a new session, after the user signs in. In subsequent calls, only the token will be available.
No. Users authenticated with the Credentials provider are not persisted in the database, and consequently the Credentials provider can only be used if JSON Web Tokens are enabled for sessions.
No. The Credentials provider does not create entries in the Account table. Account table entries are only created for OAuth/external providers. With Credentials authentication, only the User table is populated directly (if using custom database logic).
NextAuth throws an error stating 'there was no authorize() handler defined on the credential authentication provider'. The authorize() callback is required for Credentials provider to function.
If you return null from the authorize() callback, an error will be displayed advising the user to check their details. The user is redirected to the signin page with error=CredentialsSignin in the URL.
The JWT callback is invoked for requests to /api/auth/signin, /api/auth/session and calls to getSession(), getServerSession(), useSession(), but only if you are using a JWT session. This method is not invoked when you persist sessions in a database.
No. Automatic account linking on sign in is not secure between arbitrary providers and is disabled by default. If a user creates an account via Credentials and then tries to sign in with OAuth using the same email, they will get an 'OAuthAccountNotLinked' error.
The Credentials provider requires JWT (JSON Web Tokens) session strategy. It cannot work with database session strategy.
Automatic account linking is disabled because email address verification is not part of the OAuth specification and varies between providers. With automatic linking enabled, bad actors could hijack accounts by creating an OAuth account associated with another user's email address.
No. The Email provider and Credentials provider are separate providers with different technical requirements. The Email provider requires a database to store verification tokens, while the Credentials provider works with JWT sessions and doesn't persist users to a database by default.
No. As of current versions, there is no built-in solution for automatic refresh token rotation in NextAuth/Auth.js. This functionality must be implemented manually using JWT and session callbacks.
No. Users authenticated in this manner are not persisted in the database. This is why the Credentials provider can only be used with JWT sessions.
It is intended to support use cases where you have an existing system you need to authenticate users against.
Events
15 questionsNextAuth events are recommended for audit logs, reporting, handling side-effects, sending notifications, tracking user activity without affecting authentication, and triggering background processes. They should not be used for operations that affect the authentication flow.
Yes. The execution of your authentication API will be blocked by an await on your event handler. If your event handler starts any burdensome work, it should not block its own promise on that work.
The linkAccount event message contains the user object from your adapter, the account object returned from the provider, and the profile object returned from the profile callback of the OAuth provider. The content varies depending on the flow (OAuth or Email, JWT or database sessions).
The six available events are: signIn (sent on successful sign in), signOut (sent when user signs out), createUser (sent when adapter creates a new user), updateUser (sent when adapter updates an existing user), linkAccount (sent when an account is linked to a user), and session (sent at the end of a request for the current session).
All NextAuth event handlers return Awaitable<void>, meaning they can be either synchronous (returning void) or asynchronous (returning Promise<void>), but they do not return any value.
No. NextAuth events are asynchronous functions that do not return a response. They are used for side-effects like audit logging or reporting, not for modifying the authentication flow or data.
No. The createUser event is not triggered when using the Credentials provider because the Credentials provider doesn't use a database adapter by default and relies on JWT sessions. Users authenticated via Credentials are not persisted in the database, so no adapter creation happens to trigger the event.
The updateUser event is sent when the adapter is told to update an existing user. Currently, this is only sent when the user verifies their email address.
The message object in the createUser event contains the user object that was created by the adapter.
In NextAuth v4, the signOut and updateUser event signatures were changed to use the named parameters pattern instead of positional parameters.
In NextAuth v4, the signOut event uses named parameters: signOut({ token, session }). The token parameter is provided if using JWT strategy, and the session parameter is provided if using database-persisted sessions.
The signIn event message includes an isNewUser parameter that indicates whether this is a new user signing in for the first time. You can access it via destructuring: async signIn({ user, account, isNewUser }) { ... }
No. NextAuth events are server-side only and configured in the NextAuth configuration file. There is currently no direct client-side API for authentication events.
The session event is sent at the end of a request for the current session, after all processing is complete but before the response is sent to the client.
Events are asynchronous functions that do not return a response and are used for side-effects like audit logging. Callbacks are asynchronous functions that return responses and are used to control what happens when an action is performed, such as modifying tokens or sessions.
Session Management > Session Polling
15 questionsEvery tab/window maintains its own copy of the local session state. The session is NOT stored in shared storage like localStorage or sessionStorage. Any update in one tab/window triggers a message to other tabs/windows to update their own session state.
The value for refetchInterval should always be lower than the value of the session maxAge session option.
The default value is true, meaning tabs/windows will be updated and initialize the components' state when they gain or lose focus.
The default value is 2592000 seconds (60 * 60 * 24 * 30), which equals 30 days.
Both keepAlive and clientMaxAge were replaced by refetchInterval in v4, as they overlapped in functionality. The difference is that refetchInterval keeps re-fetching the session periodically in the background.
refetchInterval uses seconds as its unit of measurement, not milliseconds. For example, a value of 60 means polling every 60 seconds (1 minute).
No, these options have no effect on clients that are not signed in. They only apply to authenticated users.
Simply polling the session will make it never expire, unless the browser puts the tab in idle mode and stops the polling.
When refetchInterval is enabled and the browser is offline, the request to /api/auth/session fails, which can invalidate the session (setting it to null). To prevent this, set refetchWhenOffline to false.
It uses navigator.onLine to determine whether the device is online before attempting to poll the session.
The default value is 0, which means session polling is disabled by default.
The update() method doesn't sync between tabs as the refetchInterval and refetchOnWindowFocus options do. To trigger an update across all tabs/windows, getSession() should be called instead.
NextAuth uses a BroadcastChannel-like implementation that listens to localStorage changes. When a session update occurs, data is written to localStorage, and each tab listens to the storage event to receive the message and update its session.
If the session state has expired when refetchInterval is triggered, all open tabs/windows will be updated to reflect this expiration.
By default, session polling will keep trying even when the device has no internet access (refetchWhenOffline is effectively true by default). Setting it to false will use navigator.onLine to only poll when the device is online.
Credentials Authentication > Password Handling
15 questionsYou will encounter CALLBACK_CREDENTIALS_JWT_ERROR. The Credentials provider cannot be used with the 'database' session strategy - it must use JWT sessions.
No. The Credentials provider can only be used if JSON Web Tokens are enabled for sessions. Users authenticated with the Credentials provider are not persisted in the database.
NextAuth uses the 'double submit cookie method' with a signed HttpOnly, host-only cookie. The CSRF token returned by /api/auth/csrf must be passed as a form variable named 'csrfToken' in all POST submissions to any API endpoint.
The authorize() function receives two parameters: 1) credentials - the credentials object containing the form fields submitted by the user, and 2) req - the request object from the HTTP POST submission.
- A user object - will be persisted to the JSON Web Token and the user will be signed in. 2) null - an error will be displayed advising the user to check their details. 3) Throw an Error - the user will be sent to the error page with the error message as a query parameter.
No. NextAuth.js will not persist users or sessions in a database when using the Credentials Provider - user accounts must be created and managed outside of NextAuth.js.
Each field can have properties including: 'type' (the HTML input type like 'email', 'password', 'text'), 'label' (the display label for the field), and 'placeholder' (placeholder text for the input).
You should set NEXTAUTH_SECRET (the secret used to encrypt tokens) and NEXTAUTH_URL (the canonical URL of your site, if you are not deploying to Vercel).
You must pass a csrfToken from /api/auth/csrf in a POST request to /api/auth/callback/credentials.
bcrypt (or bcryptjs for pure JavaScript implementations). The official Next.js documentation shows using bcrypt to hash passwords before storing them in the database and using bcrypt.compare() to verify passwords during authentication.
The functionality is intentionally limited to discourage use of passwords due to the inherent security risks associated with them and the additional complexity associated with supporting usernames and passwords.
The session.jwt: boolean option was renamed to session.strategy: 'jwt' | 'database' in NextAuth v4.
When using the Credentials Provider, the user object in callbacks is the response returned from the authorize callback and the profile object is the raw body of the HTTP POST submission.
Common fields include 'id', 'name', and 'email', though you can return any custom fields you need. For example: { id: '1', name: 'J Smith', email: '[email protected]' }
You must set session.strategy to 'jwt' in the NextAuth configuration. If you are using a database, Database Sessions are enabled by default and you need to explicitly enable JWT Sessions to use the Credentials Provider.
nextauth_v5_migration
14 questionsThe NextAuth() function in v5 returns an object with four main exports:
handlers- Route Handler methods (GET, POST) used to expose endpoints for OAuth/Email providers and REST API endpoints like/api/auth/sessionauth- A universal method to interact with NextAuth.js throughout your Next.js app (works in Middleware, Server Components, Route Handlers, and API Routes)signIn- Function to sign in with a provider; redirects to sign in page if no provider is specifiedsignOut- Function to sign out users from the application
Typical usage:
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [GitHub]
})
The key architectural change in v5 is that you configure NextAuth once in a root file (like auth.ts) and export these methods to use throughout your application, rather than passing around an authOptions configuration object.
Sources:
Based on the official AuthJS migration documentation, here's the answer:
In NextAuth v5 (Auth.js), there is no direct replacement import for next-auth/middleware. Instead, you export the auth function from your NextAuth configuration and use it as middleware.
Migration pattern:
// middleware.ts (v5)
import NextAuth from "next-auth"
import authConfig from "./auth.config"
export const { auth: middleware } = NextAuth(authConfig)
// Or simply:
export default NextAuth(authConfig).auth
The key change is that instead of importing a pre-built middleware function from next-auth/middleware, you now:
- Create an edge-compatible auth configuration (typically
auth.config.ts) - Pass it to
NextAuth() - Export the resulting
.authfunction as your middleware
This is part of NextAuth v5's simplification where most authentication logic centers around a single auth() function call rather than separate imports for different contexts.
Sources:
The session cookie prefix changed from next-auth.session-token in NextAuth v4 to authjs.session-token in Auth.js v5.
This change caused existing user sessions to become invalid after upgrading because browsers don't automatically rename cookies. Users were forced to log in again after the migration.
To prevent this, you can configure v5 to use the legacy cookie name:
export const { handlers, auth, signIn, signOut } = NextAuth({
cookies: {
sessionToken: {
name: "next-auth.session-token",
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: process.env.NODE_ENV === "production",
},
},
},
// ... rest of config
})
Sources:
Based on the official search results, I can provide the verified answer:
Auth.js is the new name for NextAuth.js v5.
The project has been rebranded from "NextAuth.js" to "Auth.js" to reflect its expanded support for multiple frameworks beyond just Next.js. The official documentation has moved to authjs.dev, and the migration guide explicitly references this naming change.
Note: The npm package name remains next-auth for the Next.js integration, but the overall project is now called Auth.js.
Sources:
Based on the official Auth.js (NextAuth.js v5) migration documentation, here's how to replace getServerSession with the v5 auth() function:
Migration Steps
Before (v4):
import { authOptions } from "pages/api/auth/[...nextauth]"
import { getServerSession } from "next-auth/next"
export default async function Page() {
const session = await getServerSession(authOptions)
return <p>Welcome {session?.user.name}!</p>
}
After (v5):
import { auth } from "@/auth"
export default async function Page() {
const session = await auth()
return <p>Welcome {session?.user.name}!</p>
}
Key Changes
- No more
authOptionsparameter - Theauth()function is exported from your config file and already has access to your configuration - Simpler import - Import
authdirectly from yourauth.tsconfig file instead of fromnext-auth/next - Single function call - Just call
auth()without passing any arguments (in Server Components)
For API Routes (Pages Router)
In API routes, the signature changes from:
await getServerSession(req, res, authOptions)
To:
await auth(req, res)
The v5 auth() function leverages Next.js server-first components and standard Web APIs to simplify authentication to a single function call in most cases.
Sources:
Based on the official documentation, I now have enough information to provide an authoritative answer about preventing logout during migration.
To prevent users from being logged out when migrating from NextAuth v4 to v5, configure the cookies.sessionToken option to use the v4 cookie name:
import NextAuth from "next-auth"
export const { handlers, auth } = NextAuth({
cookies: {
sessionToken: {
name: `next-auth.session-token`, // Use v4 cookie name
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NODE_ENV === 'production'
}
}
},
// ... rest of your config
})
Why this is necessary: Auth.js v5 changed the default session cookie name from next-auth.session-token (v4) to authjs.session-token (v5). Since browsers don't automatically rename cookies, existing user sessions become invalid after upgrading, forcing all users to log in again.
Important notes:
- This is an advanced option that opts you out of built-in dynamic cookie policies
- The
secureoption should be conditional (true in production, false in development) - Cookie name should match exactly what v4 used (check for
__Secure-or__Host-prefixes if your v4 setup used them)
Sources:
In NextAuth v5, next-auth/next is no longer used. Instead, you import the auth() function directly from your own auth configuration file (typically @/auth or ./auth).
What changed:
- ❌ v4:
import { getServerSession } from "next-auth/next" - ✅ v5:
import { auth } from "@/auth"
The auth() function replaces getServerSession, getSession, withAuth, and getToken with a unified API.
Migration example:
// Before (v4)
import { getServerSession } from "next-auth/next"
import { authOptions } from "pages/api/auth/[...nextauth]"
const session = await getServerSession(context.req, context.res, authOptions)
// After (v5)
import { auth } from "@/auth"
const session = await auth()
The auth() function is exported from your NextAuth configuration:
// auth.ts
import NextAuth from "next-auth"
export const { handlers, auth } = NextAuth({ providers: [...] })
Sources:
In NextAuth v5 (now called Auth.js), the configuration file is named auth.ts (or auth.js) and is located in the root of your repository.
Key Changes from v4:
File: auth.ts (or auth.js)
Location: Project root (can also be placed in src/auth.ts if using a src directory)
The configuration is no longer done directly in the API route. Instead, you create a separate auth.ts file that exports the authentication methods:
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { auth, handlers, signIn, signOut } = NextAuth({
providers: [GitHub],
})
The API route file (app/api/auth/[...nextauth]/route.ts) then simply imports and re-exports the handlers:
export { handlers } from "@/auth"
export const { GET, POST } = handlers
This is a major architectural change from v4 where all configuration was contained within the API route file itself.
Sources:
In NextAuth v5 (Auth.js), rename NEXTAUTH_URL to AUTH_URL.
The v5 migration maintains backward compatibility through aliasing—both NEXTAUTH_URL and AUTH_URL will work, but AUTH_ is the new standard prefix. If both variables are present, AUTH_URL takes priority.
Migration:
# v4
NEXTAUTH_URL=http://localhost:3000
# v5 (recommended)
AUTH_URL=http://localhost:3000
Note: AUTH_URL is often auto-detected and may not be required in many deployments. The only truly required environment variable in v5 is AUTH_SECRET.
Sources:
In NextAuth v5 (Auth.js), NEXTAUTH_SECRET is renamed to AUTH_SECRET.
Key Details:
- Old name (v4):
NEXTAUTH_SECRET - New name (v5):
AUTH_SECRET - Backward compatibility: Both names work in v5 as aliases, but
AUTH_SECRETis the preferred naming convention going forward - Breaking change: The prefix changes from
NEXTAUTH_toAUTH_for all environment variables
This is part of a broader naming convention change in v5 where all environment variables use the AUTH_ prefix instead of NEXTAUTH_. The same applies to other variables like:
NEXTAUTH_URL→AUTH_URL- Provider secrets:
AUTH_GITHUB_SECRET,AUTH_GITHUB_ID(auto-detected)
Simply rename your environment variable from NEXTAUTH_SECRET to AUTH_SECRET in your .env file. If you need temporary backward compatibility during migration, you can keep NEXTAUTH_SECRET as it still works as an alias.
Sources:
Yes. In NextAuth v5 (Auth.js), when the auth() function is passed the res object, it automatically rotates the session expiry.
This behavior occurs whenever auth() is invoked with the response object as part of the context (e.g., in API routes or getServerSideProps in Next.js Pages Router). Each call extends the session expiry time.
Example:
import { auth } from "@/auth"
import { NextApiRequest, NextApiResponse } from "next"
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await auth(req, res) // This rotates session expiry
if (session) return res.json("Success")
return res.status(401).json("You must be logged in.")
}
Note: This is a change from v4 behavior where getToken() did not automatically rotate sessions. The default session expiry is 30 days, configurable via session.maxAge.
Sources:
Based on the official search results, I can provide the authoritative answer:
No, there are no breaking changes to the database schema in NextAuth v5.
The database schema remains compatible with v4, so existing databases do not require migration. This is explicitly designed to make the upgrade path smoother for production applications.
Optional cleanup only:
- Since OAuth 1.0 support is removed in v5, you can optionally remove the
oauth_token_secretandoauth_tokenfields from theaccounttable if they exist and are unused.
What IS breaking:
- Database adapter packages have moved from
@next-auth/*-adapterto@auth/*-adapterscope - OAuth 1.0 support is deprecated
- Minimum Next.js version is now 14.0
- API and configuration patterns have changed
The migration guide explicitly states: "NextAuth.js version 5 is a major rewrite of the next-auth package, that being said, we introduced as few breaking changes as possible" — and preserving database schema compatibility was part of that effort.
Sources:
OAuth 1.0 was deprecated in NextAuth v5 (now Auth.js v5).
As a result of dropping OAuth 1.0 support, the oauth_token_secret and oauth_token fields can be removed from the Account table in your database schema if you're not using them. NextAuth v5 only supports OAuth 2.0 going forward.
Sources:
Based on the authoritative search results from the official Auth.js migration documentation:
The database adapter package scope changed from @next-auth/*-adapter to @auth/*-adapter in NextAuth v5.
Migration example:
# v4
npm install @next-auth/prisma-adapter
# v5
npm install @auth/prisma-adapter
Import change:
// v4
import { PrismaAdapter } from "@next-auth/prisma-adapter"
// v5
import { PrismaAdapter } from "@auth/prisma-adapter"
This applies to all database adapters (Prisma, Drizzle, MongoDB, etc.). The adapters were moved to the @auth scope because they don't rely on Next.js-specific features.
Sources:
nextauth_providers
13 questionsIn NextAuth v5 (Auth.js), configure the Google OAuth provider by importing it and adding it to the providers array in your auth.ts file:
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Google],
})
Environment Variables Required:
AUTH_GOOGLE_ID- Your Google OAuth client IDAUTH_GOOGLE_SECRET- Your Google OAuth client secret
Callback URL:
Set the authorized redirect URI in Google Cloud Console to:
https://yourdomain.com/api/auth/callback/google
Advanced Configuration (for refresh tokens):
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Google({
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code"
}
}
})
]
})
Sources:
The GitHub provider in NextAuth requires the user:email scope to access private email addresses.
The default scopes for the GitHub provider are read:user and user:email. The user:email scope specifically grants access to a user's email addresses, including private ones.
Configuration:
import GitHubProvider from "next-auth/providers/github"
providers: [
GitHubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
authorization: {
params: {
scope: 'read:user user:email'
}
}
})
]
Important Note for GitHub Apps:
If you're using a GitHub App (rather than OAuth App), you must also set the "Email addresses" account permission to read-only in your GitHub App settings to access private email addresses.
Sources:
Based on the authoritative Auth.js v5 documentation, here's how to implement magic link email authentication:
Implementation Steps
1. Install Auth.js and a database adapter:
npm install next-auth @auth/prisma-adapter
2. Configure your auth.ts file with an email provider and database adapter:
import NextAuth from "next-auth"
import Resend from "next-auth/providers/resend"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
Resend({
apiKey: process.env.AUTH_RESEND_KEY,
from: "[email protected]"
})
]
})
3. For custom HTTP email providers:
import NextAuth from "next-auth"
import { sendVerificationRequest } from "./lib/authSendRequest"
export const { handlers, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
{
id: "email",
type: "email",
maxAge: 60 * 60 * 24, // Link expires in 24 hours
sendVerificationRequest,
}
]
})
4. Implement sendVerificationRequest (for custom providers):
export async function sendVerificationRequest({ identifier: email, url }) {
await fetch("https://api.sendgrid.com/v3/mail/send", {
body: JSON.stringify({
personalizations: [{ to: [{ email }] }],
from: { email: "[email protected]" },
subject: "Sign in to Your App",
content: [{
type: "text/plain",
value: `Click here to sign in: ${url}`
}]
}),
headers: {
Authorization: `Bearer ${process.env.SENDGRID_API}`,
"Content-Type": "application/json"
},
method: "POST"
})
}
Critical requirement: A database adapter is mandatory for email providers—Auth.js cannot send magic links without a database to store verification tokens.
Sources:
Based on the authoritative NextAuth.js documentation, you customize the sign-in email template by passing a custom sendVerificationRequest function to the EmailProvider configuration.
Implementation:
import EmailProvider from "next-auth/providers/email";
export default NextAuth({
providers: [
EmailProvider({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
sendVerificationRequest: async ({ identifier, url, provider }) => {
// Your custom email sending logic here
// - identifier: the email address
// - url: the verification URL to include in email
// - provider: the provider configuration
// Example: Use your preferred email service
await yourEmailService.send({
to: identifier,
from: provider.from,
subject: "Sign in to Your App",
html: `<p>Click <a href="${url}">here</a> to sign in</p>`
});
}
})
]
});
Key parameters:
identifier: The user's email addressurl: The magic link URL that must be included in the emailprovider: The provider configuration objecttheme: Optional theme object with colors for styling
The default template uses HTML with inline styles, but you can completely replace it with your own HTML, text content, or use a third-party email service like SendGrid, Resend, or Nodemailer.
Sources:
Based on the official Auth.js documentation, Resend is the recommended email service provider for magic links.
While Auth.js supports multiple email providers (Nodemailer, Sendgrid, Postmark, and ForwardEmail), the documentation specifically recommends using HTTP-based email services like Resend or Sendgrid over SMTP-based solutions like Nodemailer for better performance and reliability.
Resend appears most prominently in the documentation with a dedicated configuration guide and is listed first among the Magic Links email providers.
Sources:
Based on the official NextAuth.js documentation, I can provide a definitive answer:
No, you cannot use database sessions with the Credentials provider in NextAuth.
The Credentials provider can only be used with JWT sessions. When using the Credentials provider, users are not persisted in the database. This is a fundamental limitation of how the Credentials provider works in NextAuth.
To use the Credentials provider, you must configure NextAuth with:
session: {
strategy: "jwt"
}
Database sessions (strategy: "database") are only compatible with OAuth providers and the Email provider when used with a database adapter. The Credentials provider bypasses the database adapter entirely because it relies on custom authentication logic that you define, and NextAuth cannot securely persist credentials-based sessions in the database without compromising the security model.
Why this limitation exists: OAuth and Email providers can be linked to database records because they have verifiable external identifiers. The Credentials provider, by design, delegates all authentication logic to your custom code, making it incompatible with NextAuth's database session management system.
Sources:
NextAuth v5 (Auth.js) automatically infers OAuth provider credentials using the naming convention:
AUTH_{PROVIDER}_{TYPE}
Where:
{PROVIDER}is the uppercase name of the provider (e.g., GOOGLE, GITHUB, TWITTER){TYPE}is one of:ID,SECRET, orISSUER
Examples:
AUTH_GOOGLE_ID=your_client_id
AUTH_GOOGLE_SECRET=your_client_secret
AUTH_GITHUB_ID=your_github_client_id
AUTH_GITHUB_SECRET=your_github_client_secret
AUTH_AUTH0_ID=your_auth0_client_id
AUTH_AUTH0_SECRET=your_auth0_client_secret
AUTH_AUTH0_ISSUER=https://your-domain.auth0.com
When environment variables follow this naming pattern, you can configure providers without explicitly passing clientId and clientSecret:
// auth.ts
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import GitHub from "next-auth/providers/github"
export const { handlers, auth } = NextAuth({
providers: [Google, GitHub], // No need to pass clientId/clientSecret
})
The inference automatically maps the environment variables to the correct provider configuration.
Sources:
To force Google to always provide a refresh token in NextAuth, add these parameters to your Google provider's authorization configuration:
import GoogleProvider from "next-auth/providers/google";
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code"
}
}
})
]
Why this works:
prompt: "consent"- Forces the consent screen every time, which triggers Google to issue a new refresh tokenaccess_type: "offline"- Tells Google your app needs offline access, requiring a refresh tokenresponse_type: "code"- Ensures the OAuth 2.0 authorization code flow is used
Trade-off: Users will see the consent screen on every sign-in, not just the first time. This is necessary because Google only issues refresh tokens on the initial authorization unless you explicitly request re-consent.
Sources:
Based on the official Auth.js documentation, here's how to configure Discord OAuth in NextAuth v5:
Configuration Steps
- Set environment variables in
.env.local:
AUTH_DISCORD_ID=your_discord_client_id
AUTH_DISCORD_SECRET=your_discord_client_secret
- Configure the provider in your
auth.tsfile:
import NextAuth from "next-auth"
import Discord from "next-auth/providers/discord"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Discord],
})
- Set up callback URL in Discord Developer Portal:
https://yourdomain.com/api/auth/callback/discord
Important Notes
- NextAuth v5 is now branded as Auth.js
- The Discord provider is imported from
next-auth/providers/discord - Environment variables must be prefixed with
AUTH_for automatic detection - You need to create an OAuth application in the Discord Developer Portal at https://discord.com/developers/applications
Custom Scopes (Optional)
If you need to customize scopes beyond the defaults:
Discord({
authorization: { params: { scope: "identify email guilds" } }
})
Sources:
Based on the authoritative Auth.js documentation, here's how to configure the Credentials provider for username and password authentication in NextAuth v5:
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Credentials({
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
// Add your authentication logic here
// Query your database/API to verify credentials
const user = await getUserFromDb(credentials.username, credentials.password)
if (user) {
// Return user object with at least an id property
return user
}
// Return null if authentication fails
return null
}
})
]
})
Key points:
- The
credentialsobject defines the form fields (username, password) - The
authorizefunction receives the credentials and must return a user object on success ornullon failure - NextAuth v5 uses JWT strategy by default with Credentials (sessions aren't stored in database)
- The returned user object must contain at minimum an
idproperty
Sources:
Based on the official NextAuth.js documentation, to send emails with the Email provider you need:
1. A database adapter - The Email provider requires a database adapter to store verification tokens. It cannot work without a database.
2. Nodemailer - NextAuth does not include nodemailer as a dependency, so you must install it separately:
npm install nodemailer
3. SMTP server configuration - You need an SMTP account (ideally from a service known to work with nodemailer) with the following details configured:
- Server host
- Server port
- Username
- Password
- From address
The Email provider works by sending a verification token to the user's email address, which they must click within 24 hours to verify their account. The database adapter stores these tokens, which is why a database is mandatory.
Sources:
24 hours (86,400 seconds)
The default validity period for verification tokens in the NextAuth.js Email provider is 24 hours. After a verification token is sent to the user's email, they have 24 hours to click the link to consume the token and complete authentication. After this period, the token expires and a new one must be requested.
This can be customized using the maxAge option in the provider configuration:
maxAge: 60 * 60 * 24 // 24 hours in seconds (customizable)
Sources:
In NextAuth v5 (Auth.js), configure the GitHub OAuth provider by:
- Install the package (if not already):
npm install next-auth@beta
- Create auth.ts in your project root:
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [GitHub],
})
- Set environment variables (auto-detected by the
AUTH_prefix):
AUTH_GITHUB_ID=your_github_client_id
AUTH_GITHUB_SECRET=your_github_client_secret
AUTH_SECRET=your_random_secret
- Create the API route at
app/api/auth/[...nextauth]/route.ts:
import { handlers } from "@/auth"
export const { GET, POST } = handlers
The GitHub provider auto-detects AUTH_GITHUB_ID and AUTH_GITHUB_SECRET without explicit configuration. The callback URL is automatically set to [origin]/api/auth/callback/github.
Key v5 changes:
- Environment variables use
AUTH_prefix (notNEXTAUTH_) - Configuration file is
auth.ts(not[...nextauth].tsin pages/api) - Export
handlers,auth,signIn,signOutfor use throughout the app
Sources:
nextauth_security
12 questionsBased on the official NextAuth.js documentation, the default SameSite attribute for NextAuth cookies is "lax".
This applies to all NextAuth cookie types including:
- Session token cookie
- CSRF token cookie
- Callback URL cookie
The "lax" setting provides a balance between security and functionality, allowing cookies to be sent with top-level navigations while preventing them from being sent with cross-site subrequests.
Sources:
Based on the official NextAuth/Auth.js documentation:
Shorter JWT session expiry compensates for the inability to invalidate tokens server-side.
Since JWTs are stateless and stored client-side, you cannot easily revoke them before expiration. To invalidate a JWT, you would need to maintain a server-side blocklist of invalid tokens (at least until they expire) and check every token against this list on each request—which defeats the stateless benefit of JWTs.
The security benefit: Shorter expiry times limit the window of vulnerability if a JWT is compromised. A stolen token becomes useless faster, reducing the attack surface without requiring server-side session management.
This trade-off allows you to:
- Maintain stateless authentication (no database lookups)
- Reduce exposure from token theft/leakage
- Simplify token invalidation (just wait for expiration rather than maintaining blocklists)
NextAuth mitigates the UX downside of frequent expiration through automatic session token rotation and keep-alive mechanisms.
Sources:
NextAuth uses the double submit cookie method for CSRF protection.
This method works by:
- CSRF Token Generation: NextAuth provides a
/api/auth/csrfendpoint that returns a CSRF token - Signed HttpOnly Cookie: The CSRF token is stored in a signed, HttpOnly, host-only cookie
- Token Submission: All POST requests to NextAuth API endpoints must include the CSRF token as a form variable named
csrfToken - Double Submit Validation: The server validates that the token in the cookie matches the token submitted in the request body
- OAuth State Parameter: For OAuth 2.0 flows, the state parameter uses a hash of the CSRF token, which must match for both POST and GET calls during sign-in
Example usage:
// Get CSRF token
const response = await fetch('/api/auth/csrf');
const { csrfToken } = await response.json();
// Submit with form
<form method="post" action="/api/auth/signin/email">
<input name="csrfToken" value={csrfToken} />
<input name="email" type="email" />
<button type="submit">Sign in</button>
</form>
CSRF protection is enabled by default on all authentication routes in NextAuth.
Sources:
Based on my search of the official Auth.js (NextAuth.js) documentation:
NextAuth.js does NOT provide built-in rate limiting or brute force protection. You must implement this yourself.
Implementation Approach
Since NextAuth.js doesn't include rate limiting, you need to add it at the Next.js API route level:
1. Using Middleware (Recommended for App Router)
// middleware.ts
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(5, "10 m"), // 5 requests per 10 minutes
});
export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/api/auth/signin')) {
const ip = request.ip ?? "127.0.0.1";
const { success } = await ratelimit.limit(ip);
if (!success) {
return new Response("Too Many Requests", { status: 429 });
}
}
}
2. Using Custom API Route Wrapper
// lib/rate-limit.ts
const rateLimit = (limit: number) => {
const tokenCache = new Map();
return {
check: (identifier: string) => {
const tokenCount = tokenCache.get(identifier) || [0];
if (tokenCount[0] === 0) {
tokenCache.set(identifier, [limit - 1, Date.now()]);
}
tokenCount[0] -= 1;
return tokenCount[0] >= 0;
}
};
};
3. In Your Credentials Provider
// pages/api/auth/[...nextauth].ts
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
export default NextAuth({
providers: [
CredentialsProvider({
async authorize(credentials, req) {
// Implement rate limiting check here
const identifier = req.headers?.['x-forwarded-for'] || req.socket?.remoteAddress;
// Your rate limiting logic
// Then verify credentials
}
})
]
});
Why NextAuth.js Doesn't Include This
The official Auth.js documentation explicitly states that the Credentials provider is "intentionally limited to discourage use of passwords" and that developers must provide their own security measures including rate limiting, as Auth.js doesn't control existing credential systems.
Sources:
Based on the official Auth.js (NextAuth) documentation, here's what happens to expired access tokens when implementing refresh token rotation:
Expired Access Tokens Are Discarded and Replaced
When an access token expires, the service verifying it will ignore the expired token value, rendering it useless. Instead of prompting the user to re-authenticate, NextAuth uses the refresh token to obtain a new access token from the OAuth provider.
Implementation Flow
- Detection: The JWT callback checks if the access token has expired by comparing
Date.now()with the storedexpires_attimestamp - Refresh Request: If expired, NextAuth makes a request to the provider's token endpoint using the
refresh_token - Token Replacement: The provider returns a new
access_token(and typically a newrefresh_token) - Storage Update: The new tokens replace the old ones in the JWT/session
- Old Token Discarded: The expired access token is no longer stored or used
Key Behavior
- Expired tokens are not reused - they're simply replaced with fresh tokens from the provider
- Refresh tokens are typically single-use - after successfully refreshing, the old refresh token is also invalidated by most providers
- Race condition risk - if multiple requests attempt to refresh simultaneously, some may fail because the refresh token gets invalidated after the first successful use
Sources:
Yes, NextAuth allows you to customize individual cookie names and properties through the cookies configuration option.
You can override the default settings for any of NextAuth's cookies (sessionToken, callbackUrl, csrfToken, pkceCodeVerifier, state, nonce) by specifying:
name: Custom cookie nameoptions: Cookie properties (httpOnly, sameSite, path, secure, maxAge, domain)
Example:
// [...nextauth].js or route.ts
export default NextAuth({
cookies: {
sessionToken: {
name: `custom-session-token`,
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: true,
maxAge: 2592000 // 30 days
}
},
csrfToken: {
name: `custom-csrf-token`,
options: {
httpOnly: true,
sameSite: 'strict',
path: '/',
secure: true
}
}
}
})
Important: This is an advanced option. NextAuth warns that customizing cookies may introduce security vulnerabilities if not done correctly. Missing options will fall back to NextAuth's secure defaults.
Sources:
Based on the official NextAuth.js documentation search results:
Yes, NextAuth session cookies are httpOnly by default.
The session token cookie (next-auth.session-token or __Secure-next-auth.session-token on HTTPS) is configured with httpOnly: true by default. This prevents client-side JavaScript from accessing the session cookie, providing protection against XSS attacks.
Default configuration:
cookies: {
sessionToken: {
name: `__Secure-next-auth.session-token`, // on HTTPS
options: {
httpOnly: true, // ← Default
sameSite: 'lax',
path: '/',
secure: true
}
}
}
You can override these defaults through the cookies option in NextAuth configuration, though this is not recommended as it may introduce security vulnerabilities.
Sources:
Based on the initial search results I obtained, I can provide you with the answer:
NextAuth cookies are automatically set to secure: true when the site URL starts with https://.
The secure attribute defaults to:
truefor all site URLs that start withhttps://falsefor URLs that start withhttp://(e.g.,http://localhost:3000for development convenience)
NextAuth automatically detects the protocol from your NEXTAUTH_URL environment variable (or the detected site URL) and sets the secure cookie attribute accordingly.
When secure: true is active, cookies use the __Secure- prefix (e.g., __Secure-next-auth.session-token) and are only transmitted over HTTPS connections.
Note: Setting cookies.secure to false in production is a security risk that may allow session hijacking.
Sources:
Based on my search of the official Auth.js/NextAuth documentation, I have verified the implementation pattern for refresh token rotation with OAuth providers.
Implementation
Implement refresh token rotation using the jwt and session callbacks. Here's the verified pattern:
1. Configure OAuth Provider for Refresh Tokens
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: {
params: {
access_type: "offline",
prompt: "consent"
}
}
})
]
2. Create Token Refresh Function
async function refreshAccessToken(token) {
try {
const url = "https://oauth2.googleapis.com/token"
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
grant_type: "refresh_token",
refresh_token: token.refreshToken,
}),
})
const refreshedTokens = await response.json()
if (!response.ok) throw refreshedTokens
return {
...token,
accessToken: refreshedTokens.access_token,
accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,
}
} catch (error) {
return {
...token,
error: "RefreshAccessTokenError",
}
}
}
3. Implement JWT Callback
callbacks: {
async jwt({ token, account }) {
// Initial sign in
if (account) {
return {
accessToken: account.access_token,
accessTokenExpires: Date.now() + account.expires_in * 1000,
refreshToken: account.refresh_token,
user: token.user,
}
}
// Return previous token if not expired
if (Date.now() < token.accessTokenExpires) {
return token
}
// Refresh the access token
return refreshAccessToken(token)
},
async session({ session, token }) {
session.accessToken = token.accessToken
session.error = token.error
return session
}
}
4. Handle Errors Client-Side
const session = await getSession()
if (session?.error === "RefreshAccessTokenError") {
signIn("google") // Force re-authentication
}
Key Points
- access_type: "offline" and prompt: "consent" are required for Google to issue refresh tokens
- Some providers only issue refresh tokens once; preserve the existing token if a new one isn't returned (
refreshedTokens.refresh_token ?? token.refreshToken) - The OAuth 2.0 token endpoint URL varies by provider (Google shown above)
- Tokens are stored encrypted in JWT cookies (JWT strategy) or in database (database strategy)
- Race conditions can occur since refresh tokens are typically single-use
Sources:
Setting secure: false for NextAuth cookies in production enables session hijacking attacks through man-in-the-middle (MITM) interception.
The Risk
When secure: false, session cookies are transmitted over unencrypted HTTP connections. An attacker on the same network (public WiFi, compromised router, ISP-level) can:
- Intercept the session token in plain text
- Replay the stolen token to impersonate the user
- Gain full authenticated access to the victim's account
Default Behavior
NextAuth.js automatically sets secure: true for HTTPS URLs and secure: false only for development (http://localhost). The official documentation explicitly states:
"Using this option [secure: false] is not recommended [in production]. This is intended to support development and testing...may allow sessions to be hijacked if used in production."
Correct Configuration
Production (HTTPS): secure: true (default)
Development (HTTP): secure: false (default for localhost)
// NextAuth handles this automatically based on NEXTAUTH_URL
// Only override if you have a specific reason:
cookies: {
sessionToken: {
options: {
secure: process.env.NODE_ENV === "production"
}
}
}
Sources:
NextAuth protects against CSRF in the OAuth state parameter by using a hash of the CSRF token as the state value.
How It Works
State Parameter Contains CSRF Hash: When initiating an OAuth sign-in flow, NextAuth generates a state parameter that contains a hash of the CSRF token.
Validation on Callback: For OAuth 2.0 providers that support
checks: ["state"], the state parameter returned in the OAuth callback is checked against the one that was generated when the sign-in flow started.Must Match Both Requests: The CSRF token hash MUST match for both:
- The initial POST request (when starting sign-in)
- The GET callback request (when returning from the OAuth provider)
Double Submit Cookie Method: NextAuth uses the "double submit cookie method" for CSRF protection across all authentication routes, utilizing a signed HttpOnly, host-only cookie.
Signed with Secret: The hashing uses the
NEXTAUTH_SECRETenvironment variable to cryptographically sign tokens and cookies.
This design ensures that even if an attacker can initiate an OAuth flow, they cannot complete it without access to the legitimate user's CSRF token, effectively preventing CSRF attacks during OAuth authentication.
Sources:
Based on the authoritative NextAuth.js documentation, here's how to customize cookie configuration:
Cookie Configuration
Configure cookie security settings using the cookies option in your NextAuth configuration:
// pages/api/auth/[...nextauth].ts
import NextAuth from "next-auth"
export default NextAuth({
cookies: {
sessionToken: {
name: `__Secure-next-auth.session-token`,
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: true
}
},
callbackUrl: {
name: `__Secure-next-auth.callback-url`,
options: {
sameSite: 'lax',
path: '/',
secure: true
}
},
csrfToken: {
name: `__Host-next-auth.csrf-token`,
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: true
}
}
}
})
Cookie Options
httpOnly: (boolean) When true, prevents client-side JavaScript from accessing the cookiesecure: (boolean) When true, cookie only sent over HTTPS. Defaults to false forhttp://URLs (e.g., localhost)sameSite: ('lax' | 'strict' | 'none') Controls cross-site request behaviorpath: (string) Cookie pathdomain: (string, optional) Cookie domain
Important Notes
- Provide all options: If you customize a cookie, you must provide ALL options for that cookie
- Advanced feature: Cookie customization is not recommended unless necessary, as incorrect configuration can break authentication or introduce security vulnerabilities
- Default behavior: NextAuth.js automatically sets secure defaults based on your environment
Sources:
nextauth_sessions
12 questionsThe three possible status values returned by useSession() are:
"loading"- The session is being fetched"authenticated"- User has a valid session"unauthenticated"- User is not signed in
Example usage:
import { useSession } from "next-auth/react"
const { data: session, status } = useSession()
if (status === "loading") return <div>Loading...</div>
if (status === "authenticated") return <div>Signed in as {session.user.email}</div>
return <div>Not signed in</div>
Sources:
When using a database adapter in NextAuth, the default session strategy is "database".
Without a database adapter, NextAuth defaults to "jwt" (an encrypted JWT stored in a session cookie). However, when an adapter is configured, the strategy automatically switches to "database" without requiring explicit configuration.
With the "database" strategy:
- The session cookie contains only a
sessionTokenvalue - This token is used to look up the full session data from the database
- Sessions are persisted in the database rather than stored client-side
You can override this behavior by explicitly setting session: { strategy: "jwt" } in your NextAuth configuration, even when using an adapter.
Sources:
Based on the official Auth.js documentation, the main disadvantage of JWT session strategy compared to database sessions is:
JWT tokens cannot be invalidated or expired before their encoded expiry time. Once issued, a JWT remains valid until it expires naturally. To revoke a JWT early, you would need to maintain a server-side blocklist and check every token against it on each request - which defeats the stateless nature of JWTs.
This limitation means:
- No "sign out everywhere" functionality - You cannot remotely invalidate sessions on other devices
- No session revocation - If a user's account is compromised, you cannot immediately invalidate active JWTs
- No concurrent session limits - You cannot easily restrict how many active sessions a user has
With database sessions, you have full control to modify or delete sessions server-side at any time, enabling features like remote logout, limiting concurrent logins, and immediate session revocation.
Sources:
Based on the official NextAuth.js documentation, the session.updateAge property controls how often the session should be updated in seconds.
Key Details:
- Default value:
86400(24 hours) - Purpose: Throttles how frequently the session is written to the database to extend it, limiting write operations
- Special behavior: When set to
0, the session is updated every time - Important: This option is ignored when using JSON Web Tokens (JWT) - it only applies to database sessions
Example:
session: {
updateAge: 24 * 60 * 60, // 24 hours (default)
}
This property helps optimize database performance by preventing excessive session updates while still keeping sessions fresh.
Sources:
Based on the official NextAuth.js documentation, here's how to force JWT sessions when using a database adapter:
Set the Session Strategy to "jwt"
When you use a database adapter, NextAuth.js automatically defaults to the "database" session strategy. To force JWT sessions instead, explicitly set the session strategy in your configuration:
session: {
strategy: "jwt"
}
Key Points
- Default without adapter: JWT sessions
- Default with adapter: Database sessions
- Override: Set
session: { strategy: "jwt" }to use JWT sessions even with a database adapter present
Example Configuration
export default NextAuth({
adapter: PrismaAdapter(prisma), // or any other adapter
session: {
strategy: "jwt" // Force JWT sessions
},
// ... other options
})
This allows you to benefit from faster session lookups (no database query needed) while still using the adapter for user/account management.
Sources:
Based on the official NextAuth.js documentation, configure session refetching interval on the client side using the refetchInterval prop on the SessionProvider component.
Configuration
Set refetchInterval in seconds to control how often the client contacts the server to update the session:
import { SessionProvider } from "next-auth/react"
export default function App({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session} refetchInterval={5 * 60}>
<Component {...pageProps} />
</SessionProvider>
)
}
Default Behavior
- Default value:
0(no automatic refetching) - When set to 0: Session polling is disabled
- When set to > 0: The value specifies the interval in seconds between refetch requests
Related Option
refetchOnWindowFocus (default: true) - Controls whether the session refetches when the browser tab gains/loses focus.
Performance Consideration
Low refetchInterval values increase network traffic and server load. For sessions with 30+ day expiry (the default), automatic refetching is typically unnecessary.
Sources:
Based on the official Auth.js (NextAuth) documentation, the main advantage of JWT session strategy over database sessions is:
JWTs do not require a database to store sessions, making them faster, cheaper to run, and easier to scale.
This is particularly beneficial in serverless environments where database round-trips add latency and cost. JWT sessions avoid the need to query a database on every request to validate the session, reducing infrastructure costs and improving response times.
The tradeoff is that database sessions allow server-side modifications (like "sign out everywhere" or limiting concurrent logins), which are more difficult with JWTs since they are stateless and stored client-side.
Sources:
Based on the search results from the official Auth.js documentation, I can provide the verified answer:
The default session strategy in NextAuth v5 (Auth.js) is JWT when no database adapter is configured, and automatically switches to database when an adapter is present.
Specifically:
- Without adapter: JWT strategy (default)
- With adapter: Database strategy (default)
You can explicitly override this behavior by setting session: { strategy: "jwt" } in your configuration to force JWT even when using an adapter.
Sources:
Based on the official NextAuth.js documentation and GitHub issues:
No. You cannot revoke individual JWT sessions in NextAuth without a server-side blocklist.
According to the NextAuth.js FAQ, JWTs are stateless tokens - once issued, they remain valid until expiration. The official documentation states: "You cannot as easily expire a JSON Web Token - doing so requires maintaining a server side blocklist of invalid tokens (at least until they expire) and checking every token against the list every time a token is presented."
NextAuth's Recommended Approach:
- Use shorter session expiry times to minimize the window of validity
- Accept that JWTs cannot be immediately revoked without additional infrastructure
Why This Limitation Exists:
JWTs are self-contained and cryptographically signed. The server verifies the signature but doesn't check against a central authority. Without a blocklist/database lookup, there's no way to mark a specific token as invalid before its expiration time.
If You Need Immediate Revocation:
You must either:
- Switch to database sessions (use
session: { strategy: "database" }) - Implement your own server-side blocklist/revocation list (Redis, database table, etc.)
Sources:
The default session maxAge in NextAuth is 30 days (2,592,000 seconds).
This is expressed as 30 * 24 * 60 * 60 seconds in the configuration.
This default applies to:
- session.maxAge - How long until an idle session expires
- jwt.maxAge - The maximum age of the NextAuth.js issued JWT (defaults to session.maxAge)
Example configuration:
session: {
maxAge: 30 * 24 * 60 * 60, // 30 days (default)
}
Sources:
Based on the official NextAuth.js documentation, the <SessionProvider> component must wrap your app to use the useSession() hook.
Implementation
import { SessionProvider } from "next-auth/react"
export default function App({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
)
}
The SessionProvider uses React Context to share the session object across components and keeps the session updated and synced between tabs/windows.
Sources:
Based on the web search results, I found that NextAuth does not currently have official built-in support for dynamically setting session maxAge based on a "remember me" checkbox. There's a PR in progress (PR #13193) to add this feature, but it's not yet in the released version.
UNABLE_TO_VERIFY: The official NextAuth documentation does not provide a native method for dynamically setting session maxAge based on user preferences like a "remember me" checkbox. This is a known limitation with an open feature request (PR #13193) but no official solution currently exists in the released versions.
The community discussions indicate this is a frequently requested feature without an official implementation pattern documented by the NextAuth team.
Sources:
nextauth_adapters
10 questionsBased on the official Auth.js documentation, here's the authoritative answer:
Installation Command
Install the Drizzle adapter with:
npm install @auth/drizzle-adapter drizzle-orm
Or with your preferred package manager:
pnpm add @auth/drizzle-adapter drizzle-orm
yarn add @auth/drizzle-adapter drizzle-orm
Note: NextAuth v5 is now called Auth.js. The package @auth/drizzle-adapter is the official adapter for Auth.js (NextAuth v5).
You'll also need a database driver depending on your database choice (e.g., @neondatabase/serverless for Neon, postgres for PostgreSQL, @libsql/client for Turso, etc.).
Basic Setup
import NextAuth from "next-auth"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "./schema"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: DrizzleAdapter(db),
providers: [],
})
The adapter requires database tables that follow the Auth.js schema. Use Drizzle's schema definitions provided in the documentation and run migrations.
Sources:
The verificationTokens table is optional in DrizzleAdapter if you're not using a Magic Link provider.
This table stores tokens for passwordless email-based authentication (Magic Link). If you're using other authentication methods like OAuth providers or credentials, you can omit this table from your Drizzle schema.
Sources:
No, you cannot use PrismaAdapter directly in NextAuth middleware on Edge runtime.
Why it doesn't work:
- Next.js middleware always runs on the Edge runtime
- The standard PrismaAdapter requires Prisma Client, which cannot run in Edge runtime environments
- Even with Prisma Client 5.9.1+, database queries still fail at query time in Edge environments
The solution:
Use the "lazy initialization" pattern to split your Auth.js configuration:
- For middleware (Edge runtime): Initialize Auth.js without the adapter
- For other routes (Node.js runtime): Initialize Auth.js with PrismaAdapter and
session: { strategy: "jwt" }
You must use JWT session strategy (not database sessions) when using PrismaAdapter in a codebase with Edge middleware. The adapter will still handle user/account storage, but sessions are managed via JWT tokens instead of database entries.
Minimum version: Use @prisma/[email protected] or above if you have middleware or other edge runtime components.
Sources:
Install these packages:
npm install @prisma/client @auth/prisma-adapter
npm install prisma --save-dev
Key change in v5: The adapter package is now @auth/prisma-adapter (not @next-auth/prisma-adapter). Database adapters moved to the @auth/*-adapter scope since they don't depend on Next.js features.
After installation, configure it:
import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [],
})
You'll also need to set up your Prisma schema with the required Auth.js models (User, Account, Session, VerificationToken).
Sources:
The session table is optional in DrizzleAdapter when using JWT strategy.
When you use JWT sessions, session data is stored in the JWT token itself rather than in the database. This means the session table is not required and can be omitted from your database schema.
To use JWT strategy with DrizzleAdapter, explicitly set the session strategy:
import NextAuth from 'next-auth'
import { DrizzleAdapter } from '@auth/drizzle-adapter'
import { db } from './database'
export const { handlers, auth, signIn, signOut } = NextAuth({
session: {
strategy: 'jwt' // Explicitly set to avoid default "database" when adapter is present
},
adapter: DrizzleAdapter(db),
providers: [...]
})
Important: Even with JWT sessions, you still need the users, accounts, and verificationToken tables if you're using OAuth providers or email authentication. Only the session table becomes unnecessary with JWT strategy.
Sources:
Based on the authoritative migration documentation, in NextAuth v5 (Auth.js v5), OAuth 1.0 support has been deprecated.
The following database fields related to OAuth 1.0 can be removed from the account table:
oauth_token_secretoauth_token
These fields were previously optional and used for OAuth 1.0 authentication flows. Since v5 drops OAuth 1.0 support, you can safely remove them if you're not using OAuth 1.0 providers.
Important: These are optional removals - v5 won't break if these fields remain in your schema. They simply become unused if you've migrated away from OAuth 1.0 providers.
Sources:
Based on the official Auth.js documentation, here's how to configure PrismaAdapter in NextAuth v5:
Installation
npm install @auth/prisma-adapter
Note: NextAuth v5 uses the @auth/* package scope instead of the legacy @next-auth/* scope.
Configuration
import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
// Your auth providers here
],
})
Key Points
- Import
PrismaAdapterfrom@auth/prisma-adapter(not@next-auth/prisma-adapter) - Pass your Prisma client instance to
PrismaAdapter(prisma) - Assign the adapter to the
adapteroption in your NextAuth configuration - No breaking changes to the database schema when migrating from v4 to v5
Sources:
Based on the authoritative search results from the official Auth.js documentation:
The new package scope for database adapters in NextAuth v5 is @auth/*-adapter (changed from @next-auth/*-adapter in v4).
For example:
- Old (v4):
@next-auth/prisma-adapter - New (v5):
@auth/prisma-adapter
This change reflects that database adapters don't rely on Next.js-specific features and are part of the broader Auth.js ecosystem, which is framework-agnostic. All official adapters are now distributed under the @auth/ namespace.
Sources:
Based on the official Auth.js documentation, I can provide you with the verified answer:
Pass custom table references as the second argument to DrizzleAdapter:
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db, accounts, sessions, users, verificationTokens } from "./schema"
export const { handlers, auth } = NextAuth({
adapter: DrizzleAdapter(db, {
usersTable: users,
accountsTable: accounts,
sessionsTable: sessions,
verificationTokensTable: verificationTokens,
}),
providers: [Google],
})
The second parameter accepts an object with these properties:
usersTable- your custom users tableaccountsTable- your custom accounts tablesessionsTable- your custom sessions table (optional, only needed for database session strategy)verificationTokensTable- your custom verification tokens table
This allows you to use your own Drizzle schema definitions with custom table names instead of the default ones.
Sources:
Set session.strategy to "jwt" in your NextAuth configuration:
session: {
strategy: "jwt"
}
When you use a database adapter, NextAuth defaults to "database" strategy (storing sessions in the database). By explicitly setting strategy: "jwt", you force NextAuth to use JWT sessions (encrypted JWE tokens in cookies) instead, even with an adapter configured.
Note: The adapter will still be used for user/account persistence, but sessions will be handled via JWT rather than database lookups.
Sources:
nextauth_troubleshooting
10 questionsCreate a next-auth.d.ts file (typically in a types/ directory) and use TypeScript module augmentation to extend the Session interface:
import NextAuth, { DefaultSession } from "next-auth"
declare module "next-auth" {
interface Session {
user: {
// Add your custom properties here
id: string
role: string
} & DefaultSession["user"]
}
interface User {
// Add custom user properties
role: string
}
}
declare module "next-auth/jwt" {
interface JWT {
// Add custom JWT properties
role: string
}
}
Key points:
File location: Create
types/next-auth.d.ts(ensuretypes/is in yourtsconfig.jsontypeRoots)Preserve defaults: Use
& DefaultSession["user"]to keep default properties (name, email, image)Three interfaces to extend:
Session- for session data returned byuseSession()andgetSession()User- for the user object from your adapter/databaseJWT- for the JWT token (import fromnext-auth/jwt)
Apply in callbacks: Use these types in your NextAuth callbacks:
callbacks: {
session({ session, token }) {
session.user.id = token.sub
session.user.role = token.role
return session
},
jwt({ token, user }) {
if (user) {
token.role = user.role
}
return token
}
}
Sources:
Place the next-auth.d.ts file in the root directory of your project, or alternatively in a types/ directory.
The file should use TypeScript Module Augmentation to extend NextAuth types:
// next-auth.d.ts or types/next-auth.d.ts
import NextAuth from "next-auth"
declare module "next-auth" {
interface Session {
// Add your custom properties here
}
interface User {
// Add your custom properties here
}
}
Important: Ensure the file is included in your tsconfig.json:
{
"compilerOptions": { ... },
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"next-auth.d.ts" // or "types/next-auth.d.ts"
]
}
TypeScript will automatically pick up module augmentations from any .d.ts file included in your project's tsconfig.json. The specific location (root vs types/ directory) is a matter of project organization preference, as long as it's in the include path.
Sources:
Common NextAuth.js v5 Production Deployment Errors & Fixes
1. Missing AUTH_SECRET Error
Error: Application fails to start or throws authentication errors in production.
Fix: AUTH_SECRET is mandatory in production. Generate and set it:
npx auth secret
Add to your production environment variables:
AUTH_SECRET=your-generated-secret-here # minimum 32 characters
2. "Host must be trusted" Error
Error: Authentication fails when deployed behind a reverse proxy (Vercel, Docker, nginx, etc.).
Fix: Set AUTH_TRUST_HOST=true in your environment variables, or add trustHost: true to your Auth.js configuration:
// auth.ts
export const { auth, handlers } = NextAuth({
trustHost: true,
providers: [...]
})
3. "handlers is not a valid Route export field"
Error: Build fails with this error in Next.js App Router.
Fix: Export GET and POST handlers individually, not as a "handlers" object:
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth"
export const { GET, POST } = handlers // Correct ✓
NOT:
export { handlers } // Wrong ✗
4. Route Handler File Location
Error: Authentication endpoints return 404.
Fix: Ensure the route handler is at the correct path:
app/api/auth/[...nextauth]/route.ts
Your main auth configuration should export handlers:
// auth.ts (root or src directory)
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { auth, handlers, signIn, signOut } = NextAuth({
providers: [GitHub],
})
5. Environment Variable Naming
Note: NextAuth.js v5 uses AUTH_* prefix (legacy NEXTAUTH_* still works):
AUTH_SECRET(orNEXTAUTH_SECRET)AUTH_URL(orNEXTAUTH_URL- auto-detected in v5, usually not needed)- Provider variables:
AUTH_GITHUB_ID,AUTH_GITHUB_SECRET, etc.
6. AUTH_URL Auto-Detection
Note: Unlike v4, AUTH_URL is auto-detected from request headers in v5 and usually not required. Only set it if using a custom base path.
Sources:
Based on my web search of official NextAuth.js documentation, here's the answer:
The JWEDecryptionFailed error occurs when NextAuth.js cannot decrypt a JWT token. The primary causes are:
1. Missing NEXTAUTH_SECRET environment variable
- NextAuth.js requires this secret to encrypt JWTs and hash email verification tokens
- In production, not providing
NEXTAUTH_SECRETwill throw an error
2. Changed NEXTAUTH_SECRET with active sessions
- If you change the secret while users have active sessions encrypted with the old secret, decryption fails
- Users must log in again to create new sessions with the new secret
3. Default secret generation in development
- In development, if you rely on automatic secret generation, the secret changes with configuration changes
- This causes decryption errors for existing sessions
Fix: Set a stable NEXTAUTH_SECRET environment variable:
NEXTAUTH_SECRET=your-secret-key-here
Generate a secret with:
openssl rand -base64 32
Sources:
The session is null immediately after successful signIn with the Credentials provider because the Credentials provider requires JWT session strategy, but database session strategy may be configured by default (particularly if you have a database adapter configured).
Root Cause
The Credentials provider has a fundamental constraint: users authenticated via credentials are not persisted in the database. Consequently, the Credentials provider can only work with JWT sessions, not database sessions.
If you have a database adapter configured, NextAuth/Auth.js defaults to "database" session strategy, which is incompatible with Credentials provider, resulting in null sessions.
Solution
Explicitly configure JWT session strategy:
export default NextAuth({
session: {
strategy: "jwt"
},
providers: [
CredentialsProvider({
// your credentials config
})
]
})
Why This Design
This limitation is intentional. The NextAuth/Auth.js team deliberately restricted Credentials provider to JWT-only to discourage password-based authentication due to inherent security risks.
Sources:
The redirectTo parameter in NextAuth v5 (Auth.js) signIn() specifies where the user should be redirected after a successful sign-in.
Default behavior: If not specified, the user is redirected to the current page (the page where sign-in was initiated).
Custom redirect: Pass a relative path to redirect elsewhere after successful authentication:
await signIn("github", { redirectTo: "/dashboard" })
This works in both server components and client components in Next.js.
Important: The redirectTo value must be a relative path (e.g., /dashboard), not an absolute URL.
Sources:
In NextAuth v5 (Auth.js), create a custom sign-in page by adding a pages configuration to your auth setup:
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
pages: {
signIn: "/login", // Your custom sign-in page route
},
})
The pages object maps page types to routes. The key is the page type (signIn) and the value is the path where your custom page exists (/login).
Important: You must create an actual page at the specified route (e.g., app/login/page.tsx or pages/login.tsx).
When you call signIn() anywhere in your application without arguments, users will be redirected to your custom sign-in page instead of the default Auth.js page.
On your custom page, you can then call signIn(providerId) with specific provider IDs to trigger authentication with each provider.
Sources:
Based on the official NextAuth.js documentation, when using redirect: false with signIn(), the function returns a Promise that resolves to an object you can use to handle errors programmatically.
The returned object contains:
ok: boolean (true if signin was successful)error: string | undefined (error code for different error types)status: number (HTTP status code)url: string | null (redirect URL if successful, null if error)
Example:
const response = await signIn("credentials", {
email: email,
password: password,
redirect: false,
});
if (response?.ok) {
// Success - manually redirect
router.push("/dashboard");
} else {
// Handle error - response.error contains the error code
console.error("Login failed:", response?.error);
setError(response?.error);
}
Important: The Promise does not reject on error, so you must check the ok field in the resolved value rather than using .catch().
This return format applies specifically to email and credentials provider types when redirect: false is passed.
Sources:
Use intersection types with DefaultSession["user"] to preserve default session user properties when extending the Session interface in NextAuth.
Implementation
import NextAuth, { DefaultSession } from "next-auth"
declare module "next-auth" {
interface Session {
user: {
// Add your custom properties
address: string
// Preserve all default properties using intersection type
} & DefaultSession["user"]
}
}
Why This Works
By default, TypeScript will overwrite the existing user properties when you extend the Session interface. The & DefaultSession["user"] intersection type merges your custom properties with the original default properties (like name, email, image), ensuring nothing is lost.
Alternative Pattern
You can also explicitly list default properties:
declare module "next-auth" {
interface Session {
user: {
address: string
name?: string | null
email?: string | null
image?: string | null
}
}
}
However, the intersection type approach is preferred as it automatically includes any future default properties.
Sources:
MISSING_NEXTAUTH_API_ROUTE_ERROR
This error occurs when the [...nextauth].js or [...nextauth].ts file is not found in the expected location (pages/api/auth/ directory).
Common causes:
- File is not located at
pages/api/auth/[...nextauth].js - Incorrect filename (case sensitivity matters: must be
[...nextauth]not[...nextAuth]) - Using special ellipsis character
…instead of three dots...in the filename
For Next.js 13+ App Router:
The file should be at app/api/auth/[...nextauth]/route.ts instead.
Sources:
nextauth_middleware
10 questionsYes, you can use getServerSideProps for authentication with a non-Edge compatible database adapter.
getServerSideProps runs in the Node.js runtime (not the Edge runtime), so it has full access to Node.js features like TCP sockets that database adapters require. This means you can safely use database adapters (like PrismaAdapter, MongoDBAdapter, etc.) with getServerSideProps even if they are not Edge compatible.
The Edge compatibility limitation only applies to Next.js Middleware, which runs in the Edge runtime by default on platforms like Vercel. To handle this, Auth.js recommends a split configuration approach:
- auth.config.ts - Contains common Auth.js configuration without the database adapter (for Edge environments like Middleware)
- auth.ts - Contains the full configuration including the database adapter (for Node.js environments like
getServerSideProps, API routes)
When using getServerSideProps, you import from the main auth.ts file and can use the database adapter normally:
// In getServerSideProps
import { auth } from "@/auth"; // Full config with adapter
export async function getServerSideProps(context) {
const session = await auth(context);
// Database adapter works here
return { props: { session } };
}
For Middleware (Edge runtime), you import from auth.config.ts which uses JWT strategy and avoids database calls.
Sources:
The authorized callback in NextAuth middleware controls whether a user can access protected routes by checking their authentication status before the middleware executes.
Purpose:
- Determines if a request should proceed based on authentication state
- Acts as a gatekeeper for protected routes in your Next.js application
- The middleware function only invokes if
authorizedreturnstrue
Parameters:
The callback receives an object with:
req- The incoming requesttoken- The JWT token (if user is authenticated)
Returns:
true- User is authorized, middleware proceedsfalse- User is not authorized, can redirect to login
Example Implementation:
import { NextResponse } from "next/server"
import { auth } from "./auth"
export default auth((req) => {
// req.auth contains the session
if (!req.auth) {
return NextResponse.redirect(new URL("/login", req.url))
}
})
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}
The typical pattern checks if a token exists - if it does, the user is authenticated and authorized to proceed.
Sources:
Based on the authoritative sources from the official Auth.js documentation and Next.js GitHub discussions, here's the verified answer:
Error: "The edge runtime does not support Node.js 'crypto' module"
This error occurs when NextAuth middleware tries to use Node.js-specific modules in Next.js Edge Runtime, which only supports Web Standard APIs, not Node.js APIs like the crypto module.
Root Cause:
Next.js middleware runs in Edge Runtime by default (especially on Vercel), which is not a full Node.js environment. When NextAuth or its dependencies (database adapters, JWT libraries like jsonwebtoken, password hashing libraries like bcrypt) attempt to import Node.js's crypto module, the Edge Runtime throws this error.
Official Solution Pattern:
Auth.js documentation recommends splitting your configuration:
- Create
auth.config.ts- Contains edge-compatible Auth.js options (no database adapter) - Create
auth.ts- Main auth configuration with database adapter, must use JWT session strategy - Middleware imports from
auth.config.tsonly
Key Constraints:
- Middleware only supports
"jwt"session strategy (not"database") - Database adapters cannot run in Edge Runtime
- Use Web Crypto-compatible libraries like
joseinstead ofjsonwebtoken
Sources:
Based on the official Auth.js documentation, here's the authoritative answer:
Why You Can't Use Database Adapters Directly in NextAuth Middleware
NextAuth middleware runs in the Edge Runtime, which lacks the underlying Node.js APIs that most database adapters require. Specifically:
Missing Node.js APIs: Edge Runtime doesn't support critical Node.js features like TCP sockets, the
dnsmodule, and other low-level networking capabilities that database drivers depend on.Runtime Environment Mismatch: Most database adapters and ORMs (like Prisma, TypeORM with traditional databases) are built for the full Node.js runtime, not the constrained Edge Runtime which runs a limited subset of V8.
Database Sessions Require Adapter Access: Using database sessions (via
strategy: "database") requires querying the session table on every authentication check, which middleware needs to do—but can't because it runs at the edge.
Solution: Use a split configuration approach:
- Create an edge-compatible config (
auth.config.ts) without the adapter for middleware - Create a full config (
auth.ts) with the adapter for API routes - Use
strategy: "jwt"instead ofstrategy: "database"when the adapter is involved
This limitation exists because middleware is designed to run at the edge for performance, but edge environments deliberately exclude heavy Node.js dependencies that database clients require.
Sources:
Based on the official NextAuth.js documentation, here's how to configure matcher patterns in middleware:
Basic Setup
Create a middleware.js (or middleware.ts) file at your project root or in the src directory:
export { default } from "next-auth/middleware"
export const config = {
matcher: ["/dashboard"]
}
Matcher Pattern Syntax
Single route:
export const config = {
matcher: ["/dashboard"]
}
Multiple specific routes:
export const config = {
matcher: ["/profile", "/posts", "/settings"]
}
Nested routes (all sub-pages):
export const config = {
matcher: ["/dashboard/:path*"]
}
Directory and all nested routes:
export const config = {
matcher: ["/dashboard/", "/dashboard/:path*"]
}
The :path* wildcard syntax matches all nested routes under that path (e.g., /dashboard/settings, /dashboard/profile, etc.).
Key Points
- Without a matcher, ALL pages are protected
- The matcher property accepts an array of route patterns
- Uses Next.js matcher syntax with wildcard support (
:path*) - Only supports JWT session strategy
- Unauthenticated users are redirected to the sign-in page by default
Sources:
Based on the search results, I found the authoritative documentation from Auth.js (the official NextAuth documentation). Let me provide the verified answer:
Split Configuration Pattern for Edge Runtime Compatibility
To use NextAuth in Edge runtime (middleware), split your configuration into two files:
1. auth.config.ts - Edge-compatible configuration without adapter:
import type { NextAuthConfig } from 'next-auth';
export const authConfig = {
pages: {
signIn: '/login',
},
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
if (isOnDashboard) {
if (isLoggedIn) return true;
return false;
} else if (isLoggedIn) {
return Response.redirect(new URL('/dashboard', nextUrl));
}
return true;
},
},
providers: [], // Add providers with an empty array for now
} satisfies NextAuthConfig;
2. auth.ts - Full configuration with database adapter:
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
import { PrismaAdapter } from "@auth/prisma-adapter";
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
adapter: PrismaAdapter(prisma),
session: { strategy: 'jwt' }, // Force JWT when using adapters not Edge-compatible
providers: [
Credentials({
// ... your providers
}),
],
});
3. middleware.ts - Import only edge-compatible config:
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
export default NextAuth(authConfig).auth;
Key Points:
- The middleware imports only
auth.config.ts, avoiding the database adapter - The main
auth.tsfile includes the adapter and forcesstrategy: 'jwt' - This prevents Edge runtime errors from database clients that require Node.js APIs
Sources:
Based on the authoritative search results from authjs.dev, here's the answer:
Create an auth.config.ts file that exports a configuration object (not a NextAuth() call) containing only Edge-compatible options:
auth.config.ts:
import type { NextAuthConfig } from "next-auth"
import GitHub from "next-auth/providers/github"
export default {
providers: [GitHub]
} satisfies NextAuthConfig
What goes in auth.config.ts:
- Providers
- Callbacks
- Pages configuration
- Any options that don't require the adapter
What goes in auth.ts:
import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import authConfig from "./auth.config"
export const { auth, handlers, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
session: { strategy: "jwt" },
...authConfig,
})
Using in middleware.ts:
import NextAuth from "next-auth"
import authConfig from "./auth.config"
export const { auth: middleware } = NextAuth(authConfig)
The separation allows middleware (which runs on Edge) to use the Edge-compatible configuration, while your main auth.ts can include adapters and other Node.js-specific features.
Sources:
jose (by panva) provides the cleanest solution for JWT verification in Edge runtime.
Why jose is the authoritative choice:
The library uses Web Crypto API instead of Node.js's crypto module, making it natively compatible with Edge runtimes (Next.js Middleware, Cloudflare Workers, Deno, Bun). NextAuth/Auth.js itself uses jose internally and has replaced jsonwebtoken with jose for Edge runtime compatibility.
Example JWT verification:
import { jwtVerify } from 'jose'
const secret = new TextEncoder().encode('your-secret-key')
const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
const { payload } = await jwtVerify(jwt, secret)
Key advantages:
- Zero dependencies
- Tree-shakeable ESM
- Fully spec-compliant (RFC 7515, 7516, 7517, 7518, 7519, 8037, 8812)
- Works across Node.js, Browser, Cloudflare Workers, Deno, Bun, and Edge runtimes
Sources:
Based on the authoritative NextAuth/Auth.js documentation, here's how to use the auth() function as a middleware wrapper for custom logic:
Using auth() as Middleware Wrapper
Export the auth() function wrapping your custom middleware logic:
import { auth } from "@/auth"
export default auth((req) => {
// req.auth contains the session object
// Your custom middleware logic here
if (!req.auth && req.nextUrl.pathname !== "/login") {
const newUrl = new URL("/login", req.nextUrl.origin)
return Response.redirect(newUrl)
}
})
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}
Key Points:
- Import auth from your auth configuration file (typically
@/auth) - Wrap your middleware function - Pass a callback function to
auth()that receives the request object - Access session via req.auth - The authenticated session is available on
req.auth - Return responses - Return
Response.redirect(),NextResponse.next(), or other responses based on your logic - Add matcher config - Use the
config.matcherexport to specify which routes the middleware applies to
This pattern allows you to add custom authorization logic, redirects, header modifications, or any other middleware behavior while still having access to the authentication state.
Sources:
Based on the official Auth.js documentation, NextAuth middleware on Edge runtime requires the JWT session strategy.
The middleware only supports strategy: "jwt" for sessions. This is because:
- Edge runtime limitations - Database adapters that use the database session strategy require Node.js APIs (like TCP sockets) that are not available in Edge runtime environments
- Middleware execution context - Next.js middleware always runs in an Edge runtime, which is incompatible with traditional database connections
Configuration requirement:
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: DatabaseAdapter, // can use adapter
session: {
strategy: "jwt" // MUST use jwt strategy
},
// other config...
})
For middleware usage, the official recommendation is to use a split configuration approach where you create a base config without the database adapter for edge-compatible code, ensuring the JWT session strategy is used throughout.
Note: The middleware currently only supports session verification - full sign-in flows require the Node.js runtime.
Sources:
nextauth_callbacks
10 questionsBased on the official NextAuth.js documentation, here's how to restrict sign-ins to users with email addresses from a specific domain:
Use the signIn callback to check the email domain. The callback can return:
trueto allow sign-infalseto deny sign-in (displays default error)- A string URL to redirect to a custom error page
Example for Google Provider:
callbacks: {
async signIn({ account, profile }) {
if (account.provider === "google") {
return profile.email_verified && profile.email.endsWith("@example.com")
}
return true // Do different verification for other providers
},
}
This checks that:
- The email is verified (
profile.email_verified) - The email ends with your specific domain (
@example.com)
Example for Email Provider:
For the email provider, you can use the verificationRequest property to restrict domains before sending verification emails:
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
const isAllowedEmail = email.endsWith("@example.com")
if (isAllowedEmail) {
return true
} else {
return false
}
},
}
The callback receives different parameters depending on the provider being used. Check the account.provider value to handle different providers differently.
Sources:
Based on the official NextAuth.js documentation, the JWT callback receives different arguments on first sign-in versus subsequent calls:
First Sign-In
On the first invocation (when the user signs in), the callback receives:
token- The JWT token objectuser- The user object from the provideraccount- The account object (provider info, access tokens, etc.)profile- The profile object from the providerisNewUser- Boolean indicating if this is a new user
Subsequent Calls
On subsequent invocations (when accessing an existing session), the callback receives:
token- The JWT token object only
Detection Pattern
The official documentation recommends checking for the existence of these parameters to detect first sign-in:
async jwt({ token, user, account, profile }) {
// Persist additional data on first sign-in
if (account) {
// First sign-in - account, user, profile available
token.accessToken = account.access_token
}
// Subsequent calls - only token available
return token
}
The callback is triggered on sign-in requests (/api/auth/signin), session requests (/api/auth/session), and calls to getSession(), getServerSession(), or useSession().
Sources:
The signIn callback returns true to allow a user to sign in.
It can return three types of values:
true- Allows the user to sign infalse- Blocks sign in and displays a default error message- A string (URL path) - Blocks sign in and redirects to a custom error page (e.g.,
'/unauthorized')
Example:
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
if (isAllowedToSignIn) {
return true // Allow sign in
} else {
return false // Block with default error
// or
return '/unauthorized' // Block and redirect
}
}
}
Sources:
Use the jwt and session callbacks in NextAuth configuration. The jwt callback stores custom data in the JWT token, and the session callback exposes that data to the client.
callbacks: {
async jwt({ token, account, user }) {
// Add custom data to token on sign-in
if (account) {
token.accessToken = account.access_token
token.userId = user.id
}
return token
},
async session({ session, token }) {
// Pass token data to client-accessible session
session.accessToken = token.accessToken
session.userId = token.userId
return session
}
}
How it works:
- The
jwtcallback runs when a token is created (sign-in) or updated (session access) - Custom properties added to
tokenare encrypted and stored in a cookie - The
sessioncallback runs whengetSession(),useSession(), or/api/auth/sessionis called - Properties added to
sessionobject become accessible on the client side - Arguments like
user,accountare only available on first sign-in; subsequent calls only havetoken
Sources:
Based on the official NextAuth.js documentation, here's the answer:
The redirect callback controls where to redirect the user after they sign in or sign out. It's invoked anytime the user is redirected to a callback URL (e.g., on successful sign in or sign out).
By default, the redirect callback:
- Allows relative callback URLs starting with "/"
- Allows callback URLs on the same origin as the site
- Returns the base URL otherwise (as a security measure)
This callback serves as a security control to prevent open redirect vulnerabilities by validating redirect destinations before sending users there.
Example implementation:
callbacks: {
async redirect({ url, baseUrl }) {
// Allows relative callback URLs
if (url.startsWith("/")) return `${baseUrl}${url}`
// Allows callback URLs on the same origin
else if (new URL(url).origin === baseUrl) return url
return baseUrl
}
}
Parameters:
url: The URL to redirect to (from the callback URL parameter)baseUrl: The base URL of the site
Note: The redirect callback may be invoked more than once in the same authentication flow.
Sources:
The session callback is called whenever a session is checked. This includes:
- Any call to
getSession() - Any call to
useSession() - Any request to
/api/auth/session
The session callback runs after the JWT callback (when using JWT sessions) and allows you to control what session data is returned to the client.
Key timing detail: The jwt() callback is invoked before the session() callback, so any data added to the JWT token in the jwt() callback is available in the session() callback.
Example:
callbacks: {
async session({ session, token, user }) {
// Send properties to the client, like an access token from the JWT
session.accessToken = token.accessToken
return session
}
}
Sources:
Based on the official Auth.js documentation, the jwt callback is used to implement token refresh logic in NextAuth.
The jwt callback is invoked whenever a JWT is created or updated. This is where you:
- Check if the access token has expired
- Refresh the token using the refresh_token if needed
- Return the updated token object
Example implementation pattern:
callbacks: {
async jwt({ token, account }) {
// Initial sign in
if (account) {
return {
access_token: account.access_token,
expires_at: Math.floor(Date.now() / 1000 + account.expires_in),
refresh_token: account.refresh_token,
}
}
// Return previous token if not expired
if (Date.now() < token.expires_at * 1000) {
return token
}
// Access token has expired, refresh it
return refreshAccessToken(token)
}
}
For database session strategy, the refresh logic can also be implemented in the session callback where you check token expiration and refresh as needed.
Sources:
Based on the official NextAuth.js documentation, No, the account object is NOT available in every JWT callback invocation.
The account object is only available during the initial sign-in event. After the first invocation, subsequent JWT callback calls will only contain the token parameter.
When account is available:
- First sign-in (user authentication)
- When the callback is invoked due to sign-in events
When account is undefined:
- Subsequent requests after initial sign-in
- Regular JWT token refreshes
- Session checks
Common pattern:
async jwt({ token, account, profile }) {
// account is only available on first sign-in
if (account) {
token.accessToken = account.access_token
token.id = profile.id
}
// On subsequent calls, only token is available
return token
}
This design allows you to persist provider-specific data (like OAuth access tokens) from the account object into the JWT token during initial authentication, which can then be accessed in all subsequent invocations.
Sources:
In NextAuth.js/Auth.js, the authorized callback is used in middleware to protect routes by checking authentication status before allowing access.
Implementation
Step 1: Define the authorized callback in your auth configuration
import NextAuth from "next-auth"
export const { auth, handlers } = NextAuth({
callbacks: {
authorized: async ({ auth }) => {
// Return true if authenticated, false redirects to login
return !!auth
},
},
})
Step 2: Export auth as middleware
export { auth as middleware } from "@/auth"
Step 3: Configure protected routes (optional)
Use a matcher to protect specific routes:
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*"]
}
Advanced Usage with Custom Logic
Access the request object for path-based or method-based authorization:
callbacks: {
authorized: async ({ request, auth }) => {
const { pathname } = request.nextUrl
// Require auth for /admin routes
if (pathname.startsWith("/admin")) {
return auth?.user?.role === "admin"
}
// Handle POST requests differently
if (request.method === "POST") {
const { authToken } = (await request.json()) ?? {}
return await validateAuthToken(authToken)
}
// All other routes require authentication
return !!auth
}
}
Alternative: auth() as Middleware Wrapper
You can use auth() directly in middleware for more control:
import { auth } from "@/auth"
export default auth((req) => {
if (!req.auth && req.nextUrl.pathname !== "/login") {
const newUrl = new URL("/login", req.nextUrl.origin)
return Response.redirect(newUrl)
}
})
The authorized callback receives { auth, request } and must return true (allow), false (redirect to sign-in), or a Response object for custom handling.
Sources:
In the jwt callback, return an object with your custom data added to the token parameter. The user object is available during sign-in, allowing you to add user properties to the token:
callbacks: {
async jwt({ token, user }) {
// Add custom data during sign-in
if (user) {
token.id = user.id
token.role = user.role
// Add any custom fields
token.customField = user.customField
}
return token
}
}
To expose this data to the client, forward it in the session callback:
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id
}
return token
},
async session({ session, token }) {
// Forward token data to session
session.user.id = token.id
return session
}
}
Key points:
- The
userobject is only available during sign-in (when the user first authenticates) - On subsequent calls, you only have access to the
tokenobject - Return the modified
tokenobject to persist the data - Use the
sessioncallback to expose token data to the client - The JWT is encrypted by default via your
AUTH_SECRETenvironment variable
Sources:
nextauth_multi_provider
5 questionsBased on the official NextAuth.js documentation, the allowDangerousEmailAccountLinking option allows automatic linking of OAuth accounts that share the same email address.
Default Behavior (option disabled/false):
When a user tries to sign in with an OAuth provider (e.g., Google) and another account already exists with the same email address but different provider (e.g., GitHub), NextAuth throws an OAuthAccountNotLinked error to prevent automatic linking.
When Enabled (option set to true):
NextAuth will automatically link the OAuth account to the existing user account with the same email address, even if they're from different providers.
Security Consideration:
This option is marked "dangerous" because it assumes the OAuth provider has securely verified the email address. If an OAuth provider has weak email verification, an attacker could potentially gain access to another user's account by creating an OAuth account with the victim's email.
Configuration Example:
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
allowDangerousEmailAccountLinking: true,
}),
]
Sources:
Based on the official NextAuth.js documentation, here's what happens:
If a user is already signed in and uses signIn() with a different provider, the new provider account is automatically linked to the currently authenticated user. This happens regardless of whether the email addresses match, and this automatic linking for already-authenticated users is NOT affected by the allowDangerousEmailAccountLinking option.
However, if the user is NOT signed in:
- By default, accounts are NOT automatically linked, even if they share the same email address
- The user will see an
OAuthAccountNotLinkederror if an account with that email already exists - To enable automatic linking by email, you must set
allowDangerousEmailAccountLinking: truein your provider configuration (though this has security implications)
Key distinction:
- Already signed in → automatic linking when signing in with different provider
- Not signed in → no automatic linking by default (requires explicit configuration)
Sources:
OAuthAccountNotLinked
When attempting to sign in with a different OAuth provider using the same email address as an existing account, NextAuth throws the OAuthAccountNotLinked error.
This occurs when "the email on the account is already linked, but not with this OAuth account." NextAuth disables automatic account linking by default for security reasons—email verification standards vary across OAuth providers, and automatic linking could allow attackers to hijack accounts by creating OAuth accounts with someone else's email address.
Solution:
Set allowDangerousEmailAccountLinking: true in your provider configuration if you trust the provider has securely verified email addresses:
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
allowDangerousEmailAccountLinking: true,
}),
]
Warning: Only enable this if you trust the provider's email verification process, as it can create security vulnerabilities.
Sources:
No, automatic account linking is not secure for all OAuth providers and is disabled by default in NextAuth.js.
The Security Risk:
When automatic account linking is enabled, a malicious actor can hijack accounts by creating an OAuth account with the victim's email address. This works because:
- Email verification is not part of the OAuth specification
- Different providers handle email verification inconsistently (some verify, some don't, some return verification status)
- An email address associated with an OAuth account doesn't guarantee it belongs to the account holder
When It May Be Safe:
Automatic linking can be secure only if you explicitly trust the OAuth provider to have verified email addresses, such as:
- OAuth providers you control (e.g., internal corporate SSO)
- Specific providers you trust to properly verify emails
How to Enable (If You Accept the Risk):
Set allowDangerousEmailAccountLinking: true in your provider configuration. The name explicitly signals the security risk.
Exception - Already Authenticated Users:
If a user is already signed in and signs in with a different provider, the accounts are linked automatically regardless of the allowDangerousEmailAccountLinking setting.
Sources:
Based on the official NextAuth.js/Auth.js documentation, no specific providers are explicitly listed as "safe" for allowDangerousEmailAccountLinking. Instead, the documentation provides criteria for when it's safe to use:
Safe Scenarios (Not Specific Providers):
- OAuth providers you control - Providers that only authorize users internal to your organization
- Providers you explicitly trust to have verified the user's email address
Providers with Email Verification Data:
The following providers return email_verified metadata that you can check in the signIn callback:
- Google - Returns
email_verifiedboolean property in the OAuth profile - Zitadel - Returns
email_verifiedboolean property in the OAuth profile
Key Principle:
The documentation explicitly states: "Automatic account linking on sign in is not secure between arbitrary providers" because email verification handling varies between OAuth providers - some verify emails first, some don't, and others return metadata indicating verification status.
You should only enable allowDangerousEmailAccountLinking: true if:
- You control the OAuth provider, OR
- You explicitly trust the provider has verified email addresses, OR
- You implement additional checks using
email_verifiedor similar properties in yoursignIncallback
Sources:
nextauth_auth_methods
3 questionsBased on the official Auth.js documentation, here are the exact steps to implement passwordless magic link authentication with NextAuth.js v5 (now Auth.js):
Implementation Steps
1. Install Dependencies
npm install next-auth@beta
npm install @auth/[database]-adapter # e.g., @auth/prisma-adapter
npm install resend # or your preferred email provider
2. Set Up Database Adapter
A database is required for magic links. Configure your database schema with these tables:
- Users
- Accounts
- Sessions (optional, can use JWT sessions)
- VerificationToken (required for email auth)
3. Configure Auth.js with Email Provider
Create auth.ts:
import NextAuth from "next-auth"
import Resend from "next-auth/providers/resend"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: yourDatabaseAdapter, // Required
providers: [
Resend({
from: "[email protected]"
})
]
})
4. Set Environment Variable
AUTH_RESEND_KEY=re_xxxxxxxxxxxx
5. Add API Route Handler
Create app/api/auth/[...nextauth]/route.ts:
import { handlers } from "@/auth"
export const { GET, POST } = handlers
6. Trigger Sign In
import { signIn } from "@/auth"
await signIn("resend", { email: "[email protected]" })
How It Works
- User enters email address
- Verification token is generated and stored in database
- Email with magic link is sent (token valid for 24 hours by default)
- User clicks link
- Token is verified and user is signed in
- Account is created if it doesn't exist
Configuration Options
maxAge: Token expiration (default: 24 hours)sendVerificationRequest: Custom email template functionfrom: Sender email address
Sources:
Based on the official Auth.js documentation, here's how to implement WebAuthn/Passkeys authentication with NextAuth.js v5:
Implementation Steps
1. Install Required Dependencies
npm install @simplewebauthn/browser
2. Enable Experimental Feature
Add the experimental flag to your Auth.js configuration:
import NextAuth from "next-auth"
import Passkey from "next-auth/providers/passkey"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Passkey
],
experimental: { enableWebAuthn: true }
})
3. Database Setup
The Passkeys provider requires a database adapter and an Authenticator table. Add this table to your database schema alongside the standard Auth.js tables (User, Account, Session, VerificationToken).
4. Sign In Implementation
Use the WebAuthn-specific signIn function:
import { signIn } from "next-auth/webauthn"
// For registration/authentication
await signIn("passkey")
Important Notes:
- WebAuthn/Passkeys support is experimental and not recommended for production yet
- Requires a database adapter (Prisma, DrizzleORM, etc.)
- Built-in signin pages automatically include "Signin with Passkeys" button
- For custom UI, use the
next-auth/webauthnsignIn function
Sources:
Use the signIn callback to check if the user exists in your database before allowing authentication to proceed. Return false to block authentication for non-existent users.
// [...nextauth].js
import NextAuth from "next-auth"
import EmailProvider from "next-auth/providers/email"
export default NextAuth({
providers: [
EmailProvider({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
}),
],
callbacks: {
async signIn({ user, account, email }) {
// Check if user exists in database
const existingUser = await db.user.findUnique({
where: { email: user.email }
})
// Block sign-in if user doesn't exist
if (!existingUser) {
return false // This prevents the magic link from being sent
}
return true
}
}
})
How it works:
- The
signIncallback is triggered twice with the Email Provider:- When the user requests a magic link (before email is sent)
- When they click the magic link
- Returning
falseon the first trigger prevents the email from being sent to non-existent users - The
userobject contains the email address to check against your database - Blocked users see an "AccessDenied" error by default (customizable via error pages)
Important: The Email Provider requires a database adapter - it cannot work without one.
Sources:
migration
2 questionsTo maintain user sessions during a migration from NextAuth v4 to v5, you must explicitly configure the session cookie name in your v5 configuration to match the v4 default.
Implementation
In your Auth.js v5 configuration file (usually auth.ts), add the cookies option:
import NextAuth from "next-auth"
export const { auth, handlers, signIn, signOut } = NextAuth({
// ... providers and other config
cookies: {
sessionToken: {
name: `next-auth.session-token`,
},
},
})
Why This Works
- Cookie Name Continuity: v4 uses
next-auth.session-tokenby default. v5 usesauthjs.session-tokenby default. By forcing v5 to use the old name, it can read the existing cookies on users' browsers. - Token Compatibility: The internal structure of the JWT (JWE) is largely compatible between v4 and v5, allowing the new library to decrypt and verify the old tokens (assuming
AUTH_SECRETremains the same).
Important Prerequisites
- Same Secret: You must use the same
NEXTAUTH_SECRET(orAUTH_SECRET) environment variable. - Same Domain/Path: Ensure cookie domain and path settings haven't changed.
Sources:
When migrating from NextAuth v4 to v5 (Auth.js) without updating the session cookie configuration, users will be logged out because the default session cookie name has changed.
The Change
- v4 Default:
next-auth.session-token - v5 Default:
authjs.session-token
Because the cookie name is different, Auth.js v5 will not recognize the existing v4 session cookies. It will look for authjs.session-token, find nothing, and treat the user as unauthenticated.
Impact
- Immediate Logout: All active users will be required to sign in again.
- Session Loss: Any data stored in the session token (for JWT strategy) will be inaccessible until a new session is created.
Prevention
To prevent this, you must explicitly configure the cookie name in v5 to match the v4 default:
// auth.ts
export const { auth, handlers } = NextAuth({
cookies: {
sessionToken: {
name: `next-auth.session-token`,
},
},
// ... other config
})
Sources:
nextauth_multi_tenant
1 questionBased on my research of the official Auth.js (NextAuth v5) documentation and GitHub discussions, here's the verified configuration approach:
NextAuth.js v5 Multi-Tenant Subdomain Configuration
Configure NextAuth v5 for multi-tenant subdomain authentication using these components:
1. Enable Dynamic Host Detection
Set trustHost: true in your auth configuration or use the AUTH_TRUST_HOST=true environment variable. This allows Auth.js to trust the X-Forwarded-Host header and automatically detect the correct host for each subdomain request.
// auth.ts
import NextAuth from "next-auth"
export const { handlers, auth, signIn, signOut } = NextAuth({
trustHost: true,
// ... other config
})
2. Configure Cookies for Subdomain Sharing
Set the cookie domain with a leading dot to enable session sharing across all subdomains:
export const { handlers, auth, signIn, signOut } = NextAuth({
trustHost: true,
cookies: {
sessionToken: {
name: `__Secure-next-auth.session-token`,
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: true,
domain: '.yourdomain.com' // Leading dot enables subdomain access
}
}
},
// ... providers, callbacks, etc.
})
3. Handle Environment-Specific Configuration
For development (localhost doesn't support subdomain cookies):
const isProduction = process.env.NODE_ENV === 'production'
export const { handlers, auth, signIn, signOut } = NextAuth({
trustHost: true,
cookies: isProduction ? {
sessionToken: {
name: `__Secure-next-auth.session-token`,
options: {
domain: '.yourdomain.com',
secure: true,
// ... other options
}
}
} : undefined, // Use defaults for development
})
4. Dynamic Tenant-Based Configuration
Use lazy initialization to configure Auth.js per-request based on subdomain:
import { headers } from 'next/headers'
export const { handlers, auth } = NextAuth(async (req) => {
const headersList = await headers()
const host = headersList.get('host') || ''
const protocol = headersList.get('x-forwarded-proto') || 'https'
// Extract tenant from subdomain
const tenant = host.split('.')[0]
return {
trustHost: true,
redirectProxyUrl: `${protocol}://${host}/api/auth`,
providers: [
// Configure providers based on tenant
],
callbacks: {
async redirect({ url, baseUrl }) {
// Ensure redirects stay within tenant subdomain
const requestHost = headersList.get('host')
if (url.startsWith('/')) return `${protocol}://${requestHost}${url}`
return url
}
}
}
})
Important Notes:
- The
cookiesconfiguration is an advanced option with complex implications - The leading dot (
.yourdomain.com) is required for cross-subdomain cookie access - Browsers restrict leading dots for localhost; use custom hostnames in
/etc/hostsfor local testing trustHost: trueis safe when your deployment platform (Vercel, Cloudflare, etc.) correctly sets host headers
Sources:
nextauth_compatibility
1 questionBased on my search, I cannot provide a definitive "yes" answer with superconfidence due to documented compatibility issues.
UNABLE_TO_VERIFY: While NextAuth.js v5 officially supports Next.js 15 (minimum requirement is Next.js 14.0), there are multiple documented compatibility issues specifically with Turbopack that affect NextAuth functionality.
The issues include:
- Module parsing errors with Turbopack dev server
- Infinite request loops when using NextAuth middleware with Turbopack
- crypto.getRandomValues() errors specific to Turbopack
- Session handling problems
These are not theoretical concerns but actively reported bugs in the official repositories. While NextAuth v5 + Next.js 15 works, adding Turbopack introduces breaking issues that may require workarounds or waiting for upstream fixes.
Sources:
nextauth_v5_patterns
1 questionThe auth() method in NextAuth.js v5 (now Auth.js) is a universal function that replaces the previous fragmented authentication methods (getServerSession, getSession, withAuth, getToken) with a single, unified API that works everywhere in your Next.js application.
Setup
Export auth from your configuration file (auth.ts):
import NextAuth from "next-auth"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [/* your providers */],
})
Usage Across All Contexts
1. Server Components
import { auth } from "@/auth"
export default async function Page() {
const session = await auth()
return <p>Hello {session?.user?.name}</p>
}
2. Route Handlers (App Router)
import { auth } from "@/auth"
export const GET = auth((req) => {
if (!req.auth) return Response.json({ error: "Unauthorized" }, { status: 401 })
return Response.json({ user: req.auth.user })
})
3. Middleware
import { auth } from "@/auth"
export default auth((req) => {
if (!req.auth && req.nextUrl.pathname !== "/login") {
const newUrl = new URL("/login", req.nextUrl.origin)
return Response.redirect(newUrl)
}
})
4. API Routes (Pages Router)
import { auth } from "@/auth"
import type { NextApiRequest, NextApiResponse } from "next"
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await auth(req, res)
if (!session) return res.status(401).json({ error: "Unauthorized" })
return res.json({ user: session.user })
}
Key Benefits
- One method for all contexts: Server Components, Route Handlers, Middleware, and API Routes
- Consistent API: Returns session data with the same structure everywhere
- Type-safe: Full TypeScript support
- Simplified migration: Replaces 5+ different methods from v4
Sources:
nextauth_credentials
1 questionBased on the official NextAuth.js documentation, NextAuth.js does not provide built-in password reset functionality for the Credentials provider. This is intentional.
Implementation Approach
You must implement password reset outside of NextAuth.js as a separate flow:
Create custom password reset pages (not handled by NextAuth.js):
/forgot-password- Form to request password reset/reset-password/[token]- Form to set new password
Build the backend logic (separate from NextAuth.js):
- Generate a secure reset token (e.g., using
crypto.randomBytes()) - Store token in database with expiration timestamp
- Send reset email with token link
- Validate token and update password in database
- Generate a secure reset token (e.g., using
Keep NextAuth.js for authentication only:
- The Credentials provider handles sign-in after password is reset
- No changes needed to your NextAuth.js configuration
Why No Built-in Support
The official documentation explicitly states that the Credentials provider has "intentionally limited" functionality to discourage password use due to inherent security risks and complexity. NextAuth.js recommends OAuth providers instead, as they provide professionally maintained password management, abuse detection, and security features.
Example Pattern
// Separate API route: /api/forgot-password
export async function POST(req: Request) {
const { email } = await req.json();
const token = generateResetToken();
await saveTokenToDatabase(email, token, expiresIn24Hours);
await sendResetEmail(email, token);
return Response.json({ success: true });
}
// Separate API route: /api/reset-password
export async function POST(req: Request) {
const { token, newPassword } = await req.json();
await validateToken(token);
await updatePasswordInDatabase(token, newPassword);
return Response.json({ success: true });
}
Sources:
nextauth_ui
1 questionBased on the official NextAuth.js documentation, customize built-in pages using the theme configuration option in your NextAuth.js configuration:
import NextAuth from "next-auth"
export default NextAuth({
theme: {
colorScheme: "auto", // "auto" | "dark" | "light"
brandColor: "#346df1", // Hex color for accent/primary color
logo: "https://example.com/logo.png" // Absolute URL to logo image
}
})
Theme Properties:
colorScheme: Controls the color scheme"auto"- Follows system preference (default)"light"- Force light mode"dark"- Force dark mode
brandColor: Hex color value that sets the accent color for buttons and links on the built-in authentication pageslogo: Absolute URL to a logo image that will be displayed above the main card on sign-in, sign-out, error, and email verification pages
These options apply to NextAuth.js's default pages (sign-in, sign-out, error, verify-request) without needing to create custom page components. For more extensive customization beyond theme options, you'll need to create fully custom pages using the pages configuration option.
Sources:
nextauth_events
1 questionNextAuth.js events are asynchronous callback functions that execute after authentication operations complete. They're designed specifically for side effects like audit logging without blocking the authentication flow.
Available Event Hooks
signIn- Fires on successful sign insignOut- Fires when user signs outcreateUser- Fires when a new user is createdupdateUser- Fires when user is updated (e.g., email verified)linkAccount- Fires when an account (OAuth provider) is linked to a usersession- Fires when session is checked/active
Configuration for Audit Logging
Events are configured in the NextAuth options object:
// pages/api/auth/[...nextauth].js
import NextAuth from "next-auth"
export default NextAuth({
providers: [...],
events: {
async signIn(message) {
// message contains user, account, profile info
console.log("User signed in:", message.user.email)
// Log to database, external service, etc.
},
async signOut(message) {
console.log("User signed out:", message.token)
},
async createUser(message) {
console.log("New user created:", message.user.id)
},
async updateUser(message) {
console.log("User updated:", message.user)
},
async linkAccount(message) {
console.log("Account linked:", message.account.provider)
},
async session(message) {
// Fires whenever session is checked
// Use cautiously - can fire frequently
}
}
})
Key Characteristics
- Asynchronous: Events don't return responses
- Non-blocking: Run after the response is sent to the client
- Side-effects only: Perfect for logging, analytics, notifications
- Message object: Contains user, account, profile, token data relevant to each event
The message object structure varies by event and authentication flow (OAuth vs credentials, JWT vs database sessions).
Sources:
nextauth_testing
1 questionBased on my search of the official Auth.js/NextAuth.js documentation, I found limited authoritative guidance on unit testing and mocking. The official testing documentation focuses primarily on E2E testing rather than unit/integration test mocking strategies.
PARTIAL VERIFICATION - Official Guidance Limited
The official Auth.js testing guide recommends:
For E2E/Integration Tests:
- Run your own OAuth provider (e.g., Keycloak) for consistent testing
- Enable the Credentials provider in development mode for testing
- Use Playwright or Cypress for automated authentication flows
For Mocking (Brief Mention):
- The
customFetchconfiguration option can be used to add mocks for testing - Official recommendation: "Don't test full auth flows from CI, just mock the responses"
Common Unit Test Mocking Approaches (Community Solutions):
- Mock
useSessionhook:
jest.mock('next-auth/react', () => ({
useSession: jest.fn(() => ({
data: { user: { name: 'Test User', email: '[email protected]' } },
status: 'authenticated'
}))
}))
- Mock
getServerSession:
jest.mock('next-auth', () => ({
getServerSession: jest.fn(() => Promise.resolve({
user: { name: 'Test User', email: '[email protected]' }
}))
}))
Mock API endpoint (
/api/auth/session) for Cypress testingUse conditional providers - Add a test-only Credentials provider in development
Note: The official documentation does not provide comprehensive unit testing examples. Most specific mocking strategies come from community discussions rather than official guides.
Sources:
nextauth_jwt
1 questionUse the jwt and session callbacks in your NextAuth.js v5 configuration. The jwt callback adds custom data to the token, and the session callback exposes it to the client.
Configuration:
import NextAuth from "next-auth"
export const { handlers, auth, signIn, signOut } = NextAuth({
callbacks: {
jwt({ token, user }) {
// Add custom claims during sign-in
if (user) {
token.id = user.id
token.role = user.role // example custom claim
}
return token
},
session({ session, token }) {
// Expose claims to the client
session.user.id = token.id
session.user.role = token.role
return session
},
},
})
Key points:
- The
userparameter is only available during initial sign-in jwt()is called beforesession(), so anything in the token is available in the session callback- The JWT is encrypted (JWE) by default via
AUTH_SECRET - For TypeScript, extend the default types to include your custom claims
TypeScript types (if needed):
declare module "next-auth" {
interface User {
role?: string
}
interface Session {
user: {
id: string
role: string
}
}
}
declare module "next-auth/jwt" {
interface JWT {
id: string
role: string
}
}
Sources:
nextauth_deployment
1 questionBased on my search of the official Auth.js documentation, here's how to configure NextAuth.js v5 with Vercel Edge Runtime when using Prisma:
Split Your Configuration Into Two Files
1. Create auth.config.ts (edge-compatible, no database dependencies):
import type { NextAuthConfig } from "next-auth"
export const authConfig = {
providers: [], // Add your providers here
session: {
strategy: "jwt", // Use JWT for edge compatibility
},
// Add other common config options here
} satisfies NextAuthConfig
2. Create auth.ts (full configuration with Prisma):
import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
import { authConfig } from "./auth.config"
const prisma = new PrismaClient()
export const { handlers, auth, signIn, signOut } = NextAuth({
...authConfig,
adapter: PrismaAdapter(prisma),
})
3. In middleware.ts (import only edge-compatible config):
import NextAuth from "next-auth"
import { authConfig } from "./auth.config"
export const { auth: middleware } = NextAuth(authConfig)
Key Requirements
- Use Prisma Client version @prisma/[email protected] or above when using middleware or edge runtime
- Set session strategy to "jwt" to avoid database lookups in middleware
- Never import the full
auth.tsfile in middleware - only importauth.config.ts - The Prisma adapter and database queries only run in Node.js API routes, not in edge middleware
This pattern ensures database-dependent code runs only in Node.js runtime routes, while middleware uses JWT-based session validation that's edge-compatible.
Sources:
nextauth_nextjs_integration
1 questionUse NextAuth.js v5 (Auth.js) with Next.js Server Actions by creating a server action that calls signIn() from your auth configuration and handles credentials through FormData.
Setup auth.ts configuration:
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Credentials({
credentials: {
email: {},
password: {},
},
authorize: async (credentials) => {
// Validate credentials against your database
let user = null
// Your validation logic here
if (!user) {
throw new Error("Invalid credentials.")
}
return user
},
}),
],
})
Create a Server Action (actions.ts):
'use server'
import { signIn } from '@/auth'
import { AuthError } from 'next-auth'
export async function authenticate(
prevState: string | undefined,
formData: FormData,
) {
try {
await signIn('credentials', formData)
} catch (error) {
if (error instanceof AuthError) {
switch (error.type) {
case 'CredentialsSignin':
return 'Invalid credentials.'
default:
return 'Something went wrong.'
}
}
throw error
}
}
Use in your form component:
'use client'
import { useActionState } from 'react'
import { authenticate } from '@/actions'
export function LoginForm() {
const [errorMessage, formAction, isPending] = useActionState(
authenticate,
undefined,
)
return (
<form action={formAction}>
<input type="email" name="email" required />
<input type="password" name="password" required />
<button disabled={isPending}>Log in</button>
{errorMessage && <p>{errorMessage}</p>}
</form>
)
}
The FormData automatically captures form inputs by their name attributes. The signIn() function processes credentials server-side, and useActionState manages pending states and errors.
Sources:
nextauth_debugging
1 questionEnable NextAuth.js debug mode by setting debug: true in your NextAuth/Auth.js configuration:
import NextAuth from "next-auth"
export default NextAuth({
debug: true,
// ... other options
})
For safe deployment across environments:
export default NextAuth({
debug: process.env.NODE_ENV !== "production",
// ... other options
})
This automatically disables debug mode in production while keeping it active during development.
Interpreting Error Messages:
When debug mode is enabled, NextAuth.js will output detailed logs for:
- Authentication flow steps
- Database operations
- OAuth callback details
- Session handling
- Token generation and validation
The logs appear in your console/terminal where your Next.js application is running.
Custom Logger (Advanced):
You can override the default logger to send logs to third-party services:
export default NextAuth({
logger: {
error(code, metadata) {
// Custom error handling
},
warn(code) {
// Custom warning handling
},
debug(code, metadata) {
// Custom debug handling
}
}
})
Note: When using a custom logger, the debug option is ignored.
Security Warning: Never enable debug: true in production - it logs sensitive information including tokens and credentials.
Sources: