nextauth 100 Q&As

NextAuth.js FAQ & Answers

100 expert NextAuth.js answers researched from official documentation. Every answer cites authoritative sources you can verify.

nextauth_v5_migration

14 questions
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:

99% 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:

99% 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:

99% 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:

99% 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:

99% confidence
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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% confidence

nextauth_sessions

12 questions
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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% confidence
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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% confidence

nextauth_middleware

10 questions
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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% confidence
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:

99% confidence

nextauth_adapters

10 questions
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:

99% confidence
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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% confidence

nextauth_callbacks

10 questions
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:

99% 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:

99% 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:

99% confidence
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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% confidence

nextauth_security

10 questions
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:

99% 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:

99% confidence
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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% confidence

nextauth_authorization

9 questions
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:

99% 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:

99% 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:

99% 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:

99% confidence
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:

99% 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:

99% 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:

99% 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:

99% 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

99% confidence

nextauth_troubleshooting

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

99% 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:

99% 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:

99% confidence
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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% 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:

99% confidence