nextauth 1,779 Q&As

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) Email Authentication > Email Services (40) Database Adapters > Custom Adapters (39) OAuth Authentication > Token Handling (38) Error Handling (36) Database Adapters > Drizzle Adapter (36) Migration v4 to v5 (35) Configuration Options > Core Options (34) Session Management > Session Configuration (33) Custom Pages > Error Page (33) Next.js App Router Integration > Middleware Protection (32) Configuration Options > Cookie Configuration (32) Database Adapters > Adapter Models (32) Security > Production Checklist (30) OAuth Providers > Microsoft/Azure AD (30) WebAuthn and Passkeys (30) OAuth Providers > Google Provider (30) OAuth Providers > Discord Provider (30) Database Adapters > Prisma Adapter (30) OAuth Providers > Custom OAuth Provider (30) OAuth Authentication > OAuth Flow (30) Session Management > JWT Sessions (30) Next.js App Router Integration > Server Actions (30) Testing (29) Next.js App Router Integration > Server Components (29) Session Management > Session Data (28) Security > Cookie Security (28) API and Client Methods > signOut Method (27) Next.js App Router Integration > Route Handler Setup (27) OAuth Authentication > Provider Configuration (26) Security > Secret Management (26) Email Authentication > Email Provider Setup (26) TypeScript (25) Custom Pages > Sign In Page (25) Session Management > Database Sessions (25) OAuth Authentication > Profile Mapping (25) Email Authentication > Verification Tokens (24) Database Adapters > MongoDB Adapter (24) API and Client Methods > useSession Hook (24) Configuration Options > Environment Variables (24) OAuth Authentication > Scopes and Permissions (24) Security > CSRF Protection (23) Next.js App Router Integration > Client Components (23) API and Client Methods > signIn Method (23) API and Client Methods > getSession / auth (23) Credentials Authentication > Credentials Provider Setup (23) Callbacks > authorized Callback (23) Email Authentication > Custom Email Templates (22) OAuth Providers > GitHub Provider (20) Account Linking (20) Configuration Options > Debug and Logging (20) Credentials Authentication > Custom Validation (20) Callbacks > signIn Callback (20) Callbacks > jwt Callback (20) Callbacks > session Callback (20) Custom Pages > Verify Request Page (19) API and Client Methods > getCsrfToken (18) Callbacks > redirect Callback (17) Custom Pages > Sign Out Page (16) Credentials Authentication > Limitations (15) Events (15) Session Management > Session Polling (15) Credentials Authentication > Password Handling (15) nextauth_v5_migration (14) nextauth_providers (13) nextauth_security (12) nextauth_sessions (12) nextauth_authorization (11) nextauth_adapters (10) nextauth_troubleshooting (10) nextauth_middleware (10) nextauth_callbacks (10) nextauth_multi_provider (5) nextauth_auth_methods (3) migration (2) nextauth_multi_tenant (1) nextauth_compatibility (1) nextauth_v5_patterns (1) nextauth_credentials (1) nextauth_ui (1) nextauth_events (1) nextauth_testing (1) nextauth_jwt (1) nextauth_deployment (1) nextauth_nextjs_integration (1) nextauth_debugging (1)

OAuth Providers > Apple Provider

40 questions

Email Authentication > Email Services

40 questions
A

The 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.

Sources
95% confidence
A

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.

95% confidence

Database Adapters > Custom Adapters

39 questions

OAuth Authentication > Token Handling

38 questions
A

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

95% confidence

Error Handling

36 questions

Database Adapters > Drizzle Adapter

36 questions

Migration v4 to v5

35 questions
A

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.

Sources
95% confidence
A

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.

Sources
95% confidence
A

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.

Sources
95% confidence

Configuration Options > Core Options

34 questions

Session Management > Session Configuration

33 questions
A

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.

Sources
95% confidence

Custom Pages > Error Page

33 questions

Next.js App Router Integration > Middleware Protection

32 questions

Database Adapters > Adapter Models

32 questions

Security > Production Checklist

30 questions
A

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.

95% confidence
A

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.

Sources
95% confidence

OAuth Providers > Microsoft/Azure AD

30 questions

WebAuthn and Passkeys

30 questions
A

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

Sources
95% confidence

OAuth Providers > Google Provider

30 questions

OAuth Providers > Discord Provider

30 questions
A

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.

95% confidence

Database Adapters > Prisma Adapter

30 questions

OAuth Providers > Custom OAuth Provider

30 questions

OAuth Authentication > OAuth Flow

30 questions
A

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.

95% confidence

Session Management > JWT Sessions

30 questions
A

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.

Sources
95% confidence
A

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.

95% confidence
A

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.

95% confidence

Next.js App Router Integration > Server Actions

30 questions

Testing

29 questions
A

import { Auth, customFetch } from "@auth/core"
import GitHub from "@auth/core/providers/github"

function proxy(...args: Parameters): ReturnType {
return undici(args[0], { ...(args[1] ?? {}), dispatcher });
}

const response = await Auth(request, {
providers: [GitHub({ [customFetch]: proxy })]
})

Sources
95% confidence
A

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'}
}),
};
});

Sources
95% confidence

Next.js App Router Integration > Server Components

29 questions

Session Management > Session Data

28 questions
A

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.

Sources
95% confidence
A

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.

95% confidence
A

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

95% confidence

API and Client Methods > signOut Method

27 questions

Next.js App Router Integration > Route Handler Setup

27 questions
A

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.

Sources
95% confidence
A

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.

95% confidence
A

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.

Sources
95% confidence
A

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.

Sources
95% confidence

OAuth Authentication > Provider Configuration

26 questions

Security > Secret Management

26 questions
A

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.

95% confidence
A

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.

95% confidence
A

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.

Sources
95% confidence

Email Authentication > Email Provider Setup

26 questions
A

Yes, SMTP traffic on port 25 is blocked on Vercel, which can cause issues when deploying NextAuth applications using the default SMTP configuration.

Sources
95% confidence
A

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.

95% confidence

TypeScript

25 questions
A

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.

Sources
95% confidence

Custom Pages > Sign In Page

25 questions

Session Management > Database Sessions

25 questions

OAuth Authentication > Profile Mapping

25 questions

Email Authentication > Verification Tokens

24 questions
A

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.

95% confidence
A

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

95% confidence
A

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.

Sources
95% confidence
A

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.

95% confidence

Database Adapters > MongoDB Adapter

24 questions

API and Client Methods > useSession Hook

24 questions

Configuration Options > Environment Variables

24 questions
A

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.

Sources
95% confidence
A

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.

95% confidence
A

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.

Sources
95% confidence
A

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.

Sources
95% confidence
A

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.

Sources
95% confidence
A

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.

95% confidence

OAuth Authentication > Scopes and Permissions

24 questions
A

The 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.

Sources
95% confidence
A

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.

Sources
95% confidence
A

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.

Sources
95% confidence
A

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.

Sources
95% confidence
A

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(" ").

95% confidence
A

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".

Sources
95% confidence
A

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

Sources
95% confidence
A

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.

Sources
95% confidence
A

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.

95% confidence
A

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.

Sources
95% confidence
A

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.

Sources
95% confidence
A

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.

Sources
95% confidence

Security > CSRF Protection

23 questions

Next.js App Router Integration > Client Components

23 questions

API and Client Methods > signIn Method

23 questions
A

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

95% confidence
A

signIn<P extends RedirectableProviderType | undefined = undefined>(provider?: LiteralUnion, options?: SignInOptions, authorizationParams?: SignInAuthorizationParams): Promise<P extends RedirectableProviderType ? SignInResponse | undefined : undefined>

95% confidence

API and Client Methods > getSession / auth

23 questions
A

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

95% confidence

Credentials Authentication > Credentials Provider Setup

23 questions

Callbacks > authorized Callback

23 questions
A

Yes, the authorized callback supports async/await. It can return either a value directly or a Promise that resolves to that value, making it compatible with both synchronous and asynchronous operations.

Sources
95% confidence

Email Authentication > Custom Email Templates

22 questions
A

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.

Sources
95% confidence
A

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.

Sources
95% confidence
A

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.

95% confidence
A

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+.

95% confidence

OAuth Providers > GitHub Provider

20 questions

Account Linking

20 questions
A

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

Sources
95% confidence

Configuration Options > Debug and Logging

20 questions

Credentials Authentication > Custom Validation

20 questions
A

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' } }

95% confidence
A

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.

95% confidence
A

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.

95% confidence
A

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.

Sources
95% confidence

Callbacks > signIn Callback

20 questions

Callbacks > jwt Callback

20 questions
A

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.

95% confidence
A

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.

95% confidence
A

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.

95% confidence
A

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.

95% confidence

Callbacks > session Callback

20 questions

Custom Pages > Verify Request Page

19 questions

API and Client Methods > getCsrfToken

18 questions

Callbacks > redirect Callback

17 questions

Custom Pages > Sign Out Page

16 questions

Credentials Authentication > Limitations

15 questions
A

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.

95% confidence

Events

15 questions
A

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

95% confidence
A

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

95% confidence
A

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.

Sources
95% confidence

Session Management > Session Polling

15 questions

Credentials Authentication > Password Handling

15 questions

nextauth_v5_migration

14 questions
A

The NextAuth() function in v5 returns an object with four main exports:

  1. handlers - Route Handler methods (GET, POST) used to expose endpoints for OAuth/Email providers and REST API endpoints like /api/auth/session

  2. auth - A universal method to interact with NextAuth.js throughout your Next.js app (works in Middleware, Server Components, Route Handlers, and API Routes)

  3. signIn - Function to sign in with a provider; redirects to sign in page if no provider is specified

  4. signOut - 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:

95% confidence
A

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:

  1. Create an edge-compatible auth configuration (typically auth.config.ts)
  2. Pass it to NextAuth()
  3. Export the resulting .auth function 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:

95% confidence
A

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:

95% confidence
A

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

  1. No more authOptions parameter - The auth() function is exported from your config file and already has access to your configuration
  2. Simpler import - Import auth directly from your auth.ts config file instead of from next-auth/next
  3. 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:

95% confidence
A

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 secure option 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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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_SECRET is the preferred naming convention going forward
  • Breaking change: The prefix changes from NEXTAUTH_ to AUTH_ 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_URLAUTH_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:

95% confidence
A

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:

95% confidence
A

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_secret and oauth_token fields from the account table if they exist and are unused.

What IS breaking:

  • Database adapter packages have moved from @next-auth/*-adapter to @auth/*-adapter scope
  • 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:

95% confidence
A

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:

95% confidence

nextauth_providers

13 questions
A

In 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 ID
  • AUTH_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:

95% confidence
A

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:

95% confidence
A

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 address
  • url: The magic link URL that must be included in the email
  • provider: The provider configuration object
  • theme: 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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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, or ISSUER

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:

95% confidence
A

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 token
  • access_type: "offline" - Tells Google your app needs offline access, requiring a refresh token
  • response_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:

95% confidence
A

Based on the official Auth.js documentation, here's how to configure Discord OAuth in NextAuth v5:

Configuration Steps

  1. Set environment variables in .env.local:
AUTH_DISCORD_ID=your_discord_client_id
AUTH_DISCORD_SECRET=your_discord_client_secret
  1. Configure the provider in your auth.ts file:
import NextAuth from "next-auth"
import Discord from "next-auth/providers/discord"

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [Discord],
})
  1. 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:

95% confidence
A

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 credentials object defines the form fields (username, password)
  • The authorize function receives the credentials and must return a user object on success or null on 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 id property

Sources:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

In NextAuth v5 (Auth.js), configure the GitHub OAuth provider by:

  1. Install the package (if not already):
npm install next-auth@beta
  1. 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],
})
  1. 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
  1. 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 (not NEXTAUTH_)
  • Configuration file is auth.ts (not [...nextauth].ts in pages/api)
  • Export handlers, auth, signIn, signOut for use throughout the app

Sources:

95% confidence

nextauth_security

12 questions
A

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

95% confidence
A

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:

95% confidence
A

NextAuth uses the double submit cookie method for CSRF protection.

This method works by:

  1. CSRF Token Generation: NextAuth provides a /api/auth/csrf endpoint that returns a CSRF token
  2. Signed HttpOnly Cookie: The CSRF token is stored in a signed, HttpOnly, host-only cookie
  3. Token Submission: All POST requests to NextAuth API endpoints must include the CSRF token as a form variable named csrfToken
  4. Double Submit Validation: The server validates that the token in the cookie matches the token submitted in the request body
  5. 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:

95% confidence
A

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:

95% confidence
A

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

  1. Detection: The JWT callback checks if the access token has expired by comparing Date.now() with the stored expires_at timestamp
  2. Refresh Request: If expired, NextAuth makes a request to the provider's token endpoint using the refresh_token
  3. Token Replacement: The provider returns a new access_token (and typically a new refresh_token)
  4. Storage Update: The new tokens replace the old ones in the JWT/session
  5. 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:

95% confidence
A

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:

95% confidence
A

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:

  • true for all site URLs that start with https://
  • false for URLs that start with http:// (e.g., http://localhost:3000 for 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:

95% confidence
A

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:

95% confidence
A

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:

  1. Intercept the session token in plain text
  2. Replay the stolen token to impersonate the user
  3. 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:

95% confidence
A

NextAuth protects against CSRF in the OAuth state parameter by using a hash of the CSRF token as the state value.

How It Works

  1. 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.

  2. 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.

  3. 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)
  4. 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.

  5. Signed with Secret: The hashing uses the NEXTAUTH_SECRET environment 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:

95% confidence

nextauth_sessions

12 questions
A

The three possible status values returned by useSession() are:

  1. "loading" - The session is being fetched
  2. "authenticated" - User has a valid session
  3. "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:

95% confidence
A

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 sessionToken value
  • 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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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:

  1. Switch to database sessions (use session: { strategy: "database" })
  2. Implement your own server-side blocklist/revocation list (Redis, database table, etc.)

Sources:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence

nextauth_authorization

11 questions
A

In NextAuth v5 (Auth.js), you protect an API route by importing the auth function from your auth configuration and wrapping your route handler with it. The wrapped handler receives a request object with an auth property containing the session data.

Implementation:

import { auth } from "@/auth"
import { NextResponse } from "next/server"

export const GET = auth(function GET(req) {
  if (req.auth) return NextResponse.json(req.auth)
  return NextResponse.json({ message: "Not authenticated" }, { status: 401 })
})

How it works:

  • Wrap your route handler function with auth()
  • The request object (req) will have an auth property
  • Check req.auth - if it exists, the user is authenticated
  • If req.auth is null/undefined, the user is not authenticated
  • Return appropriate responses based on authentication status

Setup required:
Your auth.ts configuration file must export the auth function:

import NextAuth from "next-auth"

export const { auth, handlers, signIn, signOut } = NextAuth({
  providers: [/* your providers */],
})

Sources:

95% confidence
A

Based on the official NextAuth.js documentation, here's how to check if a user has a specific role in middleware:

Use withAuth from "next-auth/middleware" with an authorized callback to check the user's role from the token:

import { withAuth } from "next-auth/middleware"

export default withAuth(
  function middleware(req) {
    console.log(req.nextauth.token)
  },
  {
    callbacks: {
      authorized: ({ token }) => token?.role === "admin",
    },
  }
)

export const config = { matcher: ["/admin"] }

The middleware function only executes if the authorized callback returns true. The token contains the user's role which you must first add during authentication via the jwt and session callbacks in your NextAuth configuration.

Complete setup requires:

  1. Add role to JWT in the jwt callback
  2. Add role to session in the session callback
  3. Access token?.role in middleware's authorized callback

The middleware checks the role before the page renders, enabling server-side role-based access control.

Sources:

95% confidence
A

Callbacks should be defined in your auth.ts (or auth.js) configuration file for them to be available in middleware in NextAuth v5.

The authorized callback specifically controls authentication in middleware. Define it within the NextAuth configuration object in your auth.ts file:

import NextAuth from "next-auth"

export const { auth, handlers } = NextAuth({
  callbacks: {
    authorized: async ({ auth }) => {
      // Return true to allow access, false to redirect to sign-in
      return !!auth
    },
  },
})

Then in your middleware.ts file, export the auth function from your auth.ts configuration:

export { auth as middleware } from "./auth"

The middleware will invoke the authorized callback to determine if requests should be allowed through. All callbacks (jwt, session, signIn, authorized, etc.) are configured in the same auth.ts file where you initialize NextAuth.

Sources:

95% confidence
A

In NextAuth (Auth.js) middleware, you can redirect unauthorized users to a 403 error page by returning a NextResponse.redirect() or NextResponse.rewrite() from the authorized callback in your auth configuration.

Method 1: Using the authorized callback with NextResponse.redirect()

// auth.ts (your Auth.js configuration)
import NextAuth from "next-auth"
import { NextResponse } from "next/server"

export const { auth, handlers } = NextAuth({
  callbacks: {
    authorized({ request, auth }) {
      const isLoggedIn = !!auth?.user
      const isProtectedRoute = request.nextUrl.pathname.startsWith('/protected')
      
      if (isProtectedRoute && !isLoggedIn) {
        // Redirect to 403 page
        return NextResponse.redirect(new URL('/403', request.url))
      }
      
      return true
    }
  }
})

Method 2: Using NextResponse.rewrite() to show 403 without changing URL

callbacks: {
  authorized({ request, auth }) {
    if (!auth?.user) {
      // Rewrite to 403 page while keeping URL
      return NextResponse.rewrite(new URL('/403', request.url))
    }
    return true
  }
}

Method 3: Return JSON response with 403 status

callbacks: {
  async authorized({ request, auth }) {
    if (!auth?.user) {
      return NextResponse.json(
        { error: "Forbidden" }, 
        { status: 403 }
      )
    }
    return true
  }
}

Important: Ensure your 403 page is not protected by the same middleware, or you'll create an infinite redirect loop. You may need to exclude it in your middleware matcher:

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|403|favicon.ico).*)']
}

Sources:

95% confidence
A

Based on the official Auth.js (NextAuth v5) documentation, here's how to protect a page in getServerSideProps:

Implementation

Import the auth() function from your Auth.js configuration and pass the context object to it:

import { auth } from "@/auth"

export async function getServerSideProps(context) {
  const session = await auth(context)
  
  if (!session) {
    return {
      redirect: {
        destination: '/login',
        permanent: false,
      },
    }
  }
  
  return {
    props: { session }
  }
}

Key Changes from v4 to v5

  • v4: Used getServerSession(context.req, context.res, authOptions)
  • v5: Use auth(context) - simpler API, no need to pass authOptions

The auth() function automatically handles session rotation when passed the context containing the response object.

Sources

95% confidence
A

An Access Control List (ACL) in the context of RBAC is a data structure that maps which roles (or users) have permission to access specific resources or perform specific operations.

According to NIST, an ACL is "a mechanism that implements access control for a system resource by enumerating the identities of the system entities that are permitted to access the resources" or "a list of permissions associated with an object that specifies who or what is allowed to access the object and what operations are allowed to be performed on the object."

In RBAC implementations with NextAuth/Auth.js:

An ACL defines which roles have access to specific endpoints, pages, or resources. The typical implementation pattern:

// Example ACL structure
const acl = {
  '/admin': ['admin'],
  '/dashboard': ['admin', 'user'],
  '/api/users': ['admin'],
  '/api/profile': ['admin', 'user', 'guest']
}

The ACL is checked in Next.js middleware or API route handlers:

  1. User authenticates via NextAuth
  2. User's role is retrieved from session
  3. Middleware checks if the user's role exists in the ACL for the requested resource
  4. Access is granted or denied based on the ACL lookup

Key difference from RBAC alone: While RBAC defines roles and their general permissions, an ACL explicitly lists which specific resources each role can access. RBAC is the overall model; ACL is the implementation mechanism.

Sources:

95% confidence
A

Based on the official Auth.js documentation, here's how to add a role field to the user object in NextAuth for RBAC:

JWT Strategy (Default)

Use the callbacks option to add the role to both the JWT token and session:

import NextAuth from "next-auth"
import Google from "next-auth/providers/google"

export const { handlers, auth } = NextAuth({
  providers: [
    Google({
      profile(profile) {
        return {
          role: profile.role ?? "user", // Set role from provider or default to "user"
        }
      },
    }),
  ],
  callbacks: {
    jwt({ token, user }) {
      if (user) token.role = user.role
      return token
    },
    session({ session, token }) {
      session.user.role = token.role
      return session
    },
  },
})

Database Strategy (with Adapter)

When using a database adapter, the role is fetched from the database automatically:

callbacks: {
  session({ session, user }) {
    session.user.role = user.role
    return session
  },
}

The key difference: JWT strategy requires persisting the role through the jwt() callback first, while database strategy accesses it directly from the user object.

Sources:

95% confidence
A

Based on the official Auth.js documentation, here's the authoritative answer:

Yes, you can dynamically assign roles based on OAuth profile data in NextAuth using the jwt and session callbacks.

Implementation (JWT Strategy):

import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  callbacks: {
    async jwt({ token, user }) {
      // Add role to token on sign in
      if (user) {
        token.role = user.role // role from OAuth profile or your logic
      }
      return token
    },
    async session({ session, token }) {
      // Add role to session
      if (session?.user) {
        session.user.role = token.role
      }
      return session
    },
  },
})

For OAuth providers returning role data, use the profile() callback:

GoogleProvider({
  profile(profile) {
    return {
      id: profile.sub,
      name: profile.name,
      email: profile.email,
      image: profile.picture,
      role: profile.role ?? "user", // Extract from OAuth profile
    }
  },
})

The profile() callback transforms the OAuth provider's profile data into the user object that flows through the jwt and session callbacks, allowing you to extract and assign roles dynamically.

Sources:

95% confidence
A

Use the jwt callback to add the role to the JWT token, then use the session callback to expose it to the client:

callbacks: {
  jwt({ token, user }) {
    if (user) token.role = user.role
    return token
  },
  session({ session, token }) {
    session.user.role = token.role
    return session
  },
}

How it works:

  1. jwt callback: On sign-in, when user exists, the user.role is assigned to token.role and persisted in the JWT
  2. session callback: The token.role is then exposed to the client by assigning it to session.user.role

Persistence strategy:

  • Without a database: Role is persisted in an encrypted cookie via JWT. To update roles, users must sign in again.
  • With a database: Role is saved on the User model and read from the database on each request.

TypeScript: You'll need to extend the JWT and Session types to include the role property.

Sources:

95% confidence
A

Based on the search results, NextAuth.js does not have official built-in support for admin user impersonation. However, I can provide an authoritative implementation pattern using NextAuth.js's official callbacks and events system.

Implementation Pattern

Store both admin and impersonated user identities in the JWT token:

// pages/api/auth/[...nextauth].ts
import NextAuth from "next-auth"

export default NextAuth({
  callbacks: {
    async jwt({ token, user, trigger }) {
      // On impersonation, store original admin ID
      if (trigger === "update" && token.impersonating) {
        token.originalUserId = token.sub // Store admin's ID
        token.impersonatedUserId = token.impersonating.userId
        token.sub = token.impersonating.userId // Switch active user
      }
      
      // On sign-in, set original user
      if (user) {
        token.sub = user.id
      }
      
      return token
    },
    
    async session({ session, token }) {
      // Expose both identities to the session
      session.user.id = token.sub
      session.user.originalUserId = token.originalUserId
      session.user.isImpersonating = !!token.originalUserId
      return session
    }
  },
  
  events: {
    async signIn({ user, account, isNewUser }) {
      // Log all sign-ins including impersonation starts
      await auditLog({
        event: "SIGN_IN",
        userId: user.id,
        timestamp: new Date(),
        metadata: { account, isNewUser }
      })
    }
  }
})

Create an impersonation API route with audit logging:

// pages/api/admin/impersonate.ts
import { getServerSession } from "next-auth"
import { authOptions } from "../auth/[...nextauth]"

export default async function handler(req, res) {
  const session = await getServerSession(req, res, authOptions)
  
  if (!session?.user?.isAdmin) {
    return res.status(403).json({ error: "Unauthorized" })
  }
  
  const { targetUserId } = req.body
  
  // Audit log BEFORE impersonation
  await auditLog({
    event: "IMPERSONATION_START",
    adminUserId: session.user.id,
    targetUserId,
    timestamp: new Date(),
    ip: req.headers['x-forwarded-for'] || req.socket.remoteAddress
  })
  
  // Update session with impersonation data
  await update({ impersonating: { userId: targetUserId } })
  
  res.json({ success: true })
}

Key principle for audit logs: Log actions with both identities: "Admin Jane (ID: 123) performed action X as User Bob (ID: 456)" - never lose the original actor's identity.

Use the events configuration to automatically log all authentication events (signIn, signOut, etc.) as documented in the official NextAuth.js events system.

Sources:

95% confidence
A

Based on the official Auth.js/NextAuth.js documentation, here's how to persist user roles in the session object for client access:

Use the session and jwt callbacks in your NextAuth configuration to expose the role to the client.

For JWT Strategy:

// pages/api/auth/[...nextauth].ts
import NextAuth from "next-auth"

export default NextAuth({
  callbacks: {
    async jwt({ token, user }) {
      // Persist the role to the token on sign in
      if (user) {
        token.role = user.role
      }
      return token
    },
    async session({ session, token }) {
      // Expose the role to the client
      if (session?.user) {
        session.user.role = token.role
      }
      return session
    }
  }
})

For Database Strategy:

// pages/api/auth/[...nextauth].ts
import NextAuth from "next-auth"

export default NextAuth({
  callbacks: {
    async session({ session, user }) {
      // Expose the role from the User model to the client
      if (session?.user) {
        session.user.role = user.role
      }
      return session
    }
  }
})

The key difference: With JWT, you must persist through both jwt (to add role to token) and session (to expose to client). With database sessions, you only need the session callback since the user object comes from the database.

Sources:

95% confidence

nextauth_adapters

10 questions
A

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

95% confidence
A

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:

  1. For middleware (Edge runtime): Initialize Auth.js without the adapter
  2. 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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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

  1. Import PrismaAdapter from @auth/prisma-adapter (not @next-auth/prisma-adapter)
  2. Pass your Prisma client instance to PrismaAdapter(prisma)
  3. Assign the adapter to the adapter option in your NextAuth configuration
  4. No breaking changes to the database schema when migrating from v4 to v5

Sources:

95% confidence
A

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:

95% confidence
A

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 table
  • accountsTable - your custom accounts table
  • sessionsTable - 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:

95% confidence
A

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:

95% confidence

nextauth_troubleshooting

10 questions
A

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

  1. File location: Create types/next-auth.d.ts (ensure types/ is in your tsconfig.json typeRoots)

  2. Preserve defaults: Use & DefaultSession["user"] to keep default properties (name, email, image)

  3. Three interfaces to extend:

    • Session - for session data returned by useSession() and getSession()
    • User - for the user object from your adapter/database
    • JWT - for the JWT token (import from next-auth/jwt)
  4. 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:

95% confidence
A

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:

95% confidence
A

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 (or NEXTAUTH_SECRET)
  • AUTH_URL (or NEXTAUTH_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:

95% confidence
A

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_SECRET will 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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence

nextauth_middleware

10 questions
A

Yes, 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:

  1. auth.config.ts - Contains common Auth.js configuration without the database adapter (for Edge environments like Middleware)
  2. 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:

95% confidence
A

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 authorized returns true

Parameters:
The callback receives an object with:

  • req - The incoming request
  • token - The JWT token (if user is authenticated)

Returns:

  • true - User is authorized, middleware proceeds
  • false - 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:

95% confidence
A

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:

  1. Create auth.config.ts - Contains edge-compatible Auth.js options (no database adapter)
  2. Create auth.ts - Main auth configuration with database adapter, must use JWT session strategy
  3. Middleware imports from auth.config.ts only

Key Constraints:

  • Middleware only supports "jwt" session strategy (not "database")
  • Database adapters cannot run in Edge Runtime
  • Use Web Crypto-compatible libraries like jose instead of jsonwebtoken

Sources:

95% confidence
A

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:

  1. Missing Node.js APIs: Edge Runtime doesn't support critical Node.js features like TCP sockets, the dns module, and other low-level networking capabilities that database drivers depend on.

  2. 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.

  3. 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 of strategy: "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:

95% confidence
A

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:

95% confidence
A

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.ts file includes the adapter and forces strategy: 'jwt'
  • This prevents Edge runtime errors from database clients that require Node.js APIs

Sources:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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:

  1. Import auth from your auth configuration file (typically @/auth)
  2. Wrap your middleware function - Pass a callback function to auth() that receives the request object
  3. Access session via req.auth - The authenticated session is available on req.auth
  4. Return responses - Return Response.redirect(), NextResponse.next(), or other responses based on your logic
  5. Add matcher config - Use the config.matcher export 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:

95% confidence
A

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:

  1. 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
  2. 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:

95% confidence

nextauth_callbacks

10 questions
A

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

  • true to allow sign-in
  • false to 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:

  1. The email is verified (profile.email_verified)
  2. 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:

95% confidence
A

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 object
  • user - The user object from the provider
  • account - The account object (provider info, access tokens, etc.)
  • profile - The profile object from the provider
  • isNewUser - 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:

95% confidence
A

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 in
  • false - 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:

95% confidence
A

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 jwt callback runs when a token is created (sign-in) or updated (session access)
  • Custom properties added to token are encrypted and stored in a cookie
  • The session callback runs when getSession(), useSession(), or /api/auth/session is called
  • Properties added to session object become accessible on the client side
  • Arguments like user, account are only available on first sign-in; subsequent calls only have token

Sources:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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:

  1. Check if the access token has expired
  2. Refresh the token using the refresh_token if needed
  3. 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:

95% confidence
A

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:

95% confidence
A

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:

95% confidence
A

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 user object is only available during sign-in (when the user first authenticates)
  • On subsequent calls, you only have access to the token object
  • Return the modified token object to persist the data
  • Use the session callback to expose token data to the client
  • The JWT is encrypted by default via your AUTH_SECRET environment variable

Sources:

95% confidence

nextauth_multi_provider

5 questions
A

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

95% confidence
A

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 OAuthAccountNotLinked error if an account with that email already exists
  • To enable automatic linking by email, you must set allowDangerousEmailAccountLinking: true in 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:

95% confidence
A

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:

95% confidence
A

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:

  1. Email verification is not part of the OAuth specification
  2. Different providers handle email verification inconsistently (some verify, some don't, some return verification status)
  3. 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:

95% confidence
A

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

  1. OAuth providers you control - Providers that only authorize users internal to your organization
  2. 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_verified boolean property in the OAuth profile
  • Zitadel - Returns email_verified boolean 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_verified or similar properties in your signIn callback

Sources:

95% confidence

nextauth_auth_methods

3 questions
A

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/webauthn signIn function

Sources:

95% confidence

migration

2 questions
A

To 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

  1. Cookie Name Continuity: v4 uses next-auth.session-token by default. v5 uses authjs.session-token by default. By forcing v5 to use the old name, it can read the existing cookies on users' browsers.
  2. 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_SECRET remains the same).

Important Prerequisites

  • Same Secret: You must use the same NEXTAUTH_SECRET (or AUTH_SECRET) environment variable.
  • Same Domain/Path: Ensure cookie domain and path settings haven't changed.

Sources:

95% confidence

nextauth_multi_tenant

1 question
A

Based 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 cookies configuration 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/hosts for local testing
  • trustHost: true is safe when your deployment platform (Vercel, Cloudflare, etc.) correctly sets host headers

Sources:

95% confidence

nextauth_compatibility

1 question
A

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

95% confidence

nextauth_v5_patterns

1 question
A

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

95% confidence

nextauth_credentials

1 question
A

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

  1. 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
  2. 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
  3. 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:

95% confidence

nextauth_ui

1 question
A

Based 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 pages

  • logo: 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:

95% confidence

nextauth_events

1 question
A

NextAuth.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 in
  • signOut - Fires when user signs out
  • createUser - Fires when a new user is created
  • updateUser - Fires when user is updated (e.g., email verified)
  • linkAccount - Fires when an account (OAuth provider) is linked to a user
  • session - 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:

95% confidence

nextauth_testing

1 question
A

Based 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 customFetch configuration 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):

  1. Mock useSession hook:
jest.mock('next-auth/react', () => ({
  useSession: jest.fn(() => ({
    data: { user: { name: 'Test User', email: '[email protected]' } },
    status: 'authenticated'
  }))
}))
  1. Mock getServerSession:
jest.mock('next-auth', () => ({
  getServerSession: jest.fn(() => Promise.resolve({
    user: { name: 'Test User', email: '[email protected]' }
  }))
}))
  1. Mock API endpoint (/api/auth/session) for Cypress testing

  2. Use 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:

95% confidence

nextauth_jwt

1 question
A

Use 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 user parameter is only available during initial sign-in
  • jwt() is called before session(), 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:

95% confidence

nextauth_deployment

1 question
A

Based 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.ts file in middleware - only import auth.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:

95% confidence

nextauth_nextjs_integration

1 question
A

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

95% confidence

nextauth_debugging

1 question
A

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

95% confidence