-
-
Notifications
You must be signed in to change notification settings - Fork 184
Description
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 onnext-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
- Create
next-auth.state
cookie during/api/auth/signin/keycloak
. - Redirect to Keycloak.
- Return to
/api/auth/callback/keycloak
with valid state. - Exchange the code for tokens.
Currently, step 1 fails: no cookie is set or read, causing the callback to fail.
🔍 Steps to Reproduce
- Create a new Nuxt 3 project.
- Install
@sidebase/nuxt-auth
. - Add the configuration above in
server/api/auth/[...].ts
. - Set up Keycloak client with callback
http://localhost:3000/api/auth/callback/keycloak
. - 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
- Create a new Nuxt 3 project.
- Install
@sidebase/nuxt-auth
. - Add the configuration above in
server/api/auth/[...].ts
. - Set up Keycloak client with callback
http://localhost:3000/api/auth/callback/keycloak
. - 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 onnext-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.'
}