Skip to content

Bug Report: OAUTH_CALLBACK_ERROR – “State cookie was missing” when using Keycloak with nuxt-auth #1061

@cesarbenjamindotnet

Description

@cesarbenjamindotnet

Environment

🐛 Bug Report: OAUTH_CALLBACK_ERROR – “State cookie was missing” when using Keycloak with nuxt-auth

Description

After reinstall npm i last friday and re-running our Nuxt 3 app, the OAuth callback started failing with the error:

ERROR [next-auth][error][OAUTH_CALLBACK_ERROR]
State cookie was missing.

This integration was working perfectly until last Friday, with no configuration changes made on the client or Keycloak side since then.


🔥 Full Error Log

ERROR  [next-auth][error][OAUTH_CALLBACK_ERROR] 4:30:50 PM
https://next-auth.js.org/errors#oauth_callback_error
State cookie was missing. {
  error: {
    TypeError: State cookie was missing.
      at Object.use (/node_modules/next-auth/core/lib/oauth/checks.js:103:23)
      at oAuthCallback (/node_modules/next-auth/core/lib/oauth/callback.js:89:25)
      at async Object.callback (/node_modules/next-auth/core/routes/callback.js:52:11)
      at async AuthHandler (/node_modules/next-auth/core/index.js:201:28)
      at async Object.handler (/.nuxt/dev/index.mjs:2071:24)
      at async file:///node_modules/h3/dist/index.mjs:2004:19
  },
  providerId: 'keycloak',
  message: 'State cookie was missing.'
}

🧩 Environment

  • Framework: Nuxt 3
  • Auth library: @sidebase/nuxt-auth (based on next-auth)
  • Mode: nuxt dev (local SSR)
  • OAuth Provider: Keycloak
  • Keycloak version: 26.0.x
  • Client types tested: both Public and Confidential
  • Callback URL: /api/auth/callback/keycloak

⚙️ Relevant Configuration

// server/api/auth/[...].ts
import { NuxtAuthHandler } from '#auth'
import KeycloakProvider from 'next-auth/providers/keycloak'

const isDev = process.env.NODE_ENV !== 'production'
const appBase = (process.env.NUXT_APP_BASE_URL || '/').replace(/\/+$/, '/')
const publicBase = process.env.NUXT_PUBLIC_BASE_URL || 'http://localhost:3000'
const fullBase = `${publicBase}${appBase}`

export default NuxtAuthHandler({
  secret: process.env.NUXT_AUTH_SECRET,
  providers: [
    // @ts-expect-error required for SSR
    KeycloakProvider.default({
      clientId: process.env.KEYCLOAK_CLIENT_ID,
      issuer: process.env.KEYCLOAK_ISSUER,
      clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
    }),
  ],
  cookies: {
    state: {
      name: 'next-auth.state',
      options: {
        httpOnly: true,
        sameSite: 'lax',
      },
    },
  },
  debug: true,
  baseURL: `${isDev ? appBase : fullBase}api/auth`,
  originEnvKey: isDev ? undefined : 'NUXT_AUTH_ORIGIN',
  globalAppMiddleware: false,
})

Environment variables

NUXT_AUTH_SECRET=•••••••••••••••••••••
KEYCLOAK_ISSUER=https://iam.dev.••••••••••/realms/myrealm
KEYCLOAK_CLIENT_ID=myid-client
KEYCLOAK_CLIENT_SECRET=•••••••••••••••••••••
NUXT_APP_BASE_URL=/
NUXT_PUBLIC_BASE_URL=http://localhost:3000

🧪 Tests & Observations

Attempt Result
Public client (no secret) Same error
Confidential client (with secret) Same error
localhost vs 127.0.0.1 Same error
sameSite: 'none' + secure: false No change
Cookies checked via DevTools next-auth.state not created during /signin/keycloak
Keycloak logs OAuth flow completes until redirect to callback
Checked checks.js source Fails at use(state) because state is undefined

🧠 Hypotheses

  • SSR + dev mode issue: the state cookie may not persist correctly through redirects in Nuxt’s H3 server.
  • Callback parameter mismatch: Keycloak adds session_state, iss, etc., which could invalidate the state comparison.
  • appBase concatenation issue: path inconsistencies may generate invalid callback URLs (//api/auth).
  • PKCE flow incompatibility: Keycloak may require client_secret even for public clients.

✅ Expected Behavior

  1. Create next-auth.state cookie during /api/auth/signin/keycloak.
  2. Redirect to Keycloak.
  3. Return to /api/auth/callback/keycloak with valid state.
  4. Exchange the code for tokens.

Currently, step 1 fails: no cookie is set or read, causing the callback to fail.


🔍 Steps to Reproduce

  1. Create a new Nuxt 3 project.
  2. Install @sidebase/nuxt-auth.
  3. Add the configuration above in server/api/auth/[...].ts.
  4. Set up Keycloak client with callback http://localhost:3000/api/auth/callback/keycloak.
  5. Run npm run dev and open /api/auth/signin/keycloak.

🚩 Result

The flow breaks with the error:
State cookie was missing


🧩 Technical Analysis

This seems to happen because the H3 dev server doesn’t persist cookies correctly across redirects in the OAuth flow.
On production builds, Next.js automatically maintains cookie state, but in Nuxt the SSR layer may not replicate this properly.


🙏 Request

  • Please confirm if this behavior is expected in nuxt-auth or if this is a bug in cookie handling during the OAuth flow.
  • Any suggested workarounds (custom cookie strategy, origin override, or middleware patch) would be appreciated.

Labels: bug, nuxt3, keycloak, oauth, state-cookie, sidebase-auth

Reproduction

🔍 Steps to Reproduce

  1. Create a new Nuxt 3 project.
  2. Install @sidebase/nuxt-auth.
  3. Add the configuration above in server/api/auth/[...].ts.
  4. Set up Keycloak client with callback http://localhost:3000/api/auth/callback/keycloak.
  5. Run npm run dev and open /api/auth/signin/keycloak.

Describe the bug

After upgrading and re-running our Nuxt 3 app, the OAuth callback started failing with the error:

ERROR [next-auth][error][OAUTH_CALLBACK_ERROR]
State cookie was missing.

This integration was working perfectly until last Friday, with no configuration changes made on the client or Keycloak side since then.


🔥 Full Error Log

ERROR  [next-auth][error][OAUTH_CALLBACK_ERROR] 4:30:50 PM
https://next-auth.js.org/errors#oauth_callback_error
State cookie was missing. {
  error: {
    TypeError: State cookie was missing.
      at Object.use (/node_modules/next-auth/core/lib/oauth/checks.js:103:23)
      at oAuthCallback (/node_modules/next-auth/core/lib/oauth/callback.js:89:25)
      at async Object.callback (/node_modules/next-auth/core/routes/callback.js:52:11)
      at async AuthHandler (/node_modules/next-auth/core/index.js:201:28)
      at async Object.handler (/.nuxt/dev/index.mjs:2071:24)
      at async file:///node_modules/h3/dist/index.mjs:2004:19
  },
  providerId: 'keycloak',
  message: 'State cookie was missing.'
}

Additional context

🧩 Environment

  • Framework: Nuxt 3
  • Auth library: @sidebase/nuxt-auth (based on next-auth)
  • Mode: nuxt dev (local SSR)
  • OAuth Provider: Keycloak
  • Keycloak version: 26.0.x
  • Client types tested: both Public and Confidential
  • Callback URL: /api/auth/callback/keycloak

⚙️ Relevant Configuration

// server/api/auth/[...].ts
import { NuxtAuthHandler } from '#auth'
import KeycloakProvider from 'next-auth/providers/keycloak'

const isDev = process.env.NODE_ENV !== 'production'
const appBase = (process.env.NUXT_APP_BASE_URL || '/').replace(/\/+$/, '/')
const publicBase = process.env.NUXT_PUBLIC_BASE_URL || 'http://localhost:3000'
const fullBase = `${publicBase}${appBase}`

export default NuxtAuthHandler({
  secret: process.env.NUXT_AUTH_SECRET,
  providers: [
    // @ts-expect-error required for SSR
    KeycloakProvider.default({
      clientId: process.env.KEYCLOAK_CLIENT_ID,
      issuer: process.env.KEYCLOAK_ISSUER,
      clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
    }),
  ],
  cookies: {
    state: {
      name: 'next-auth.state',
      options: {
        httpOnly: true,
        sameSite: 'lax',
      },
    },
  },
  debug: true,
  baseURL: `${isDev ? appBase : fullBase}api/auth`,
  originEnvKey: isDev ? undefined : 'NUXT_AUTH_ORIGIN',
  globalAppMiddleware: false,
})

Environment variables

NUXT_AUTH_SECRET=•••••••••••••••••••••
KEYCLOAK_ISSUER=https://iam.dev.••••••••••/realms/sigic
KEYCLOAK_CLIENT_ID=sigic-frontend
KEYCLOAK_CLIENT_SECRET=•••••••••••••••••••••
NUXT_APP_BASE_URL=/
NUXT_PUBLIC_BASE_URL=http://localhost:3000

Logs

ERROR  [next-auth][error][OAUTH_CALLBACK_ERROR] 4:30:50 PM
https://next-auth.js.org/errors#oauth_callback_error
State cookie was missing. {
  error: {
    TypeError: State cookie was missing.
      at Object.use (/node_modules/next-auth/core/lib/oauth/checks.js:103:23)
      at oAuthCallback (/node_modules/next-auth/core/lib/oauth/callback.js:89:25)
      at async Object.callback (/node_modules/next-auth/core/routes/callback.js:52:11)
      at async AuthHandler (/node_modules/next-auth/core/index.js:201:28)
      at async Object.handler (/.nuxt/dev/index.mjs:2071:24)
      at async file:///node_modules/h3/dist/index.mjs:2004:19
  },
  providerId: 'keycloak',
  message: 'State cookie was missing.'
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA bug that needs to be resolvedpendingAn issue waiting for triage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions