Skip to content

auth() Returns Null in App Router Despite Valid Session Cookie and Database Session (#12848 Follow-Up) #12894

@lwensveen

Description

@lwensveen

Environment

  System:
    OS: macOS 15.4.1
    CPU: (8) arm64 Apple M1
    Memory: 101.52 MB / 8.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.19.0 - /opt/homebrew/opt/node@20/bin/node
    npm: 10.8.2 - /opt/homebrew/opt/node@20/bin/npm
    pnpm: 10.6.5 - ~/Library/pnpm/pnpm
    bun: 1.2.10 - /opt/homebrew/bin/bun
  Browsers:
    Brave Browser: 135.1.77.97
    Chrome: 135.0.7049.96
    Safari: 18.4
  npmPackages:
    @auth/drizzle-adapter: ^1.9.0 => 1.9.0
    @auth/unstorage-adapter: ^2.0.0 => 2.8.0
    next: latest => 15.3.1
    next-auth: beta => 5.0.0-beta.26
    react: ^18.2.0 => 18.3.1

Reproduction URL

https://github.com/lwensveen/next-auth-example/tree/drizzle-credentials-session-issue

Describe the issue

In NextAuth v5 (next-auth@5.0.0-beta.25, @auth/core@0.37.2), auth() returns null in Server Components and server-side contexts (e.g., API fetches) in the App Router, despite a valid authjs.session-token cookie and a corresponding session in the database (sessionTable). This causes No active session errors and redirect loops to the sign-in page, breaking protected routes. The issue is a follow-up to discussion #12848, which describes similar session handling problems with the Drizzle adapter.

I’ve created a minimal reproduction in lwensveen/next-auth-example, showing that after a successful login via the Credentials provider, auth() fails to recognize the session, even though the cookie is set and the session exists in the database. Manual session creation in the jwt callback (as a workaround) creates a session, but auth() still returns null, suggesting a bug in the Drizzle adapter or App Router session retrieval.

How to reproduce

# Install Dependencies
npm install

# Set Up PostgreSQL
# Install PostgreSQL or use a service (e.g., Neon, Supabase).
# Create a database named next_auth_example:
psql -U postgres
CREATE DATABASE next_auth_example;

# Update .env.local with:
# .env.local
DATABASE_URL=postgres://postgres:password@localhost:5432/next_auth_example
AUTH_SECRET=your-secure-secret-here
NEXT_PUBLIC_API_URL=http://localhost:3000

# Generate AUTH_SECRET:
openssl rand -base64 32

# Run Migrations:
npx drizzle-kit generate
npx drizzle-kit migrate

# Create a Test User
# Generate a bcrypt hash for password123:
node -e "require('bcrypt').hash('password123', 10, (err, hash) => console.log(hash))"

# Insert user:
psql -U postgres -d next_auth_example
INSERT INTO users (id, email, password_hash, created_at) VALUES (
  gen_random_uuid(),
  'test@test.nl',
  '$2b$10$your_hashed_password',
  NOW()
);

# Start the App:
npm run dev

# Reproduce the Issue:
# 1. Visit http://localhost:3000/auth/signin.
# 2. Log in with email: test@test.com, password: password123.
# 3. Verify the authjs.session-token cookie is set (browser dev tools: Application > Cookies).
# 4. Navigate to http://localhost:3000/protected.
# 5. Observe:
#    - Redirect to /auth/signin (307) or No active session error.
#    - Console logs show session: null for ProtectedPage session:, fetchData session:, or Middleware session:.
# 6. Check the database:
psql -U postgres -d next_auth_example -c "SELECT * FROM session"
#    - A session exists with sessionToken, userId, and expires.

# Expected Behavior
# - After login, the authjs.session-token cookie is set, and a session is created in sessionTable.
# - Navigating to /protected loads the page with session data and fetched data (e.g., /api/data).
# - auth() returns a valid session in Server Components and server-side fetches, using the cookie to retrieve the session from sessionTable.

# Actual Behavior
# - Login succeeds (/api/auth/callback/credentials returns 200), and the authjs.session-token cookie is set.
# - A session is created in sessionTable (via manual jwt callback workaround).
# - Navigating to /protected triggers a 307 redirect to /auth/signin, or fetchData throws No active session.
# - Console logs show session: null for ProtectedPage session:, fetchData session:, and Middleware session:.
# - SELECT * FROM session confirms a valid session exists, but auth() fails to retrieve it.

# Logs
# Console Logs (example):
Authorized user: {
  id: 'b3ada650-3833-4d6c-a27f-a8242d031658',
  email: 'test@test.nl',
  name: null,
  image: null,
  emailVerified: null,
  createdAt: '2025-04-22T09:26:26.126Z',
  updatedAt: '2025-04-22T09:26:26.126Z'
}
jwt callback - token: {
  name: null,
  email: 'test@test.com,
  picture: null,
  sub: 'b3ada650-3833-4d6c-a27f-a8242d031658'
} user: {
  id: 'b3ada650-3833-4d6c-a27f-a8242d031658',
  email: 'test@test.com,
  name: null,
  image: null,
  emailVerified: null,
  createdAt: '2025-04-22T09:26:26.126Z',
  updatedAt: '2025-04-22T09:26:26.126Z'
} account: {
  providerAccountId: 'b3ada650-3833-4d6c-a27f-a8242d031658',
  type: 'credentials',
  provider: 'credentials'
} trigger: signIn session: undefined
No session found for user b3ada650-3833-4d6c-a27f-a8242d031658
ProtectedPage session: null
fetchData session: null
Middleware session: null
Fetched /api/data: Error: No active session

# Database:
SELECT * FROM session;
 sessionToken | userId | expires
-------------+--------+---------
 <token>     | b3ada650-3833-4d6c-a27f-a8242d031658 | 2025-05-22T09:26:26.126Z

# Cookie:
# - Name: authjs.session-token
# - Value: <valid-token>
# - Attributes: HttpOnly; SameSite=Lax; Path=/; Secure=false

### Expected behavior

It should log in with a valid session.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtriageUnseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions