Open MarkLyck opened 3 months ago
I patched the next-auth
and @auth/core
libraries with some more logs. This time i also deleted all of my browser history and cookies before the test.
Here's the full sequence of logs I gathered leading up to the error:
correct idp hint
🔈 ~ next-auth / signIn / authorizationParams: { kc_idp_hint: 'eas', identity_provider: 'eas', idp_hint: 'eas' }
🔈 ~ @auth / Auth / internalRequest.url: URL {
href: 'https://sso.yyy.xxx.com/api/auth/signin/yyy-sso-xxx?kc_idp_hint=eas&identity_provider=eas&idp_hint=eas',
origin: 'https://sso.rogers.colonynetworks.com/',
protocol: 'https:',
username: '',
password: '',
host: 'sso.yyy.xxx.com',
hostname: 'sso.yyy.xxx.com',
port: '',
pathname: '/api/auth/signin/yyy-sso-xxx',
search: '?kc_idp_hint=eas&identity_provider=eas&idp_hint=eas',
searchParams: URLSearchParams { 'kc_idp_hint' => 'eas', 'identity_provider' => 'eas', 'idp_hint' => 'eas' },
hash: ''
}
🔈 ~ @auth / Auth / internalRequest.url.searchParams: URLSearchParams { 'kc_idp_hint' => 'eas', 'identity_provider' => 'eas', 'idp_hint' => 'eas' }
correct keycloak URL
🔈 ~ @auth / actions / signIn / signInUrl: https://sso.yyy.xxx.com/api/auth/signin
correct provider
🔈 ~ @auth / actions / getAuthorizationUrl / provider: {
id: 'yyy-sso-xxx',
name: 'Keycloak',
type: 'oidc',
style: { brandColor: '#428bca' },
clientId: 'frontend-standard-flow-app',
clientSecret: 'REQUIRED_BY_NEXT_AUTH_BUT_UNUSED',
issuer: 'https://sso.xxx.com/auth/realms/yyy',
signinUrl: 'https://sso.yyy.xxx.com/api/auth/signin/yyy-sso-xxx',
callbackUrl: 'https://sso.yyy.xxx.com/api/auth/callback/yyy-sso-xxx',
redirectProxyUrl: undefined,
wellKnown: 'https://sso.xxx.com/auth/realms/yyy/.well-known/openid-configuration',
authorization: undefined,
token: undefined,
checks: [ 'pkce' ],
userinfo: undefined,
profile: [Function: re],
account: [Function: rt]
}
🔈 ~ @auth / actions / getAuthorizationUrl / url: undefined
🔈 ~ @auth / actions / getAuthorizationUrl / url.searchParams: URLSearchParams {}
🔈 ~ @auth / actions / getAuthorizationUrl / redirect_uri: https://sso.yyy.xxx.com/api/auth/callback/yyy-sso-xxx
✅ ~ @auth / actions / getAuthorizationUrl / pkce check is happening: true
🔐 ~ @auth / actions / getAuthorizationUrl / checks.pkce.create {
debug: true,
pages: {},
theme: { colorScheme: 'auto', logo: '', brandColor: '', buttonText: '' },
basePath: '/api/auth',
trustHost: true,
secret: 'brA/qMwPxzJiy13rkpSWtJQ3HIb+bh+yCCl3FH5C8hU=',
providers: [ all of my providers were listed here ]
seems to create the PKCE code verifier correctly
[90m[auth][debug]:[0m CREATE_PKCECODEVERIFIER {
"value": "0xVG98aVaKu-F-46k0oqvDBuYlSZIhMSwb3WRZXkVzA",
"maxAge": 900
}
pkce has a vale in getAuthorizationUrl
, but it doesn't match the one generated above?
🔐 ~ @auth / actions / getAuthorizationUrl / pkce value MCROqQtkYxoQoxKNmm61tce9CdyqhDjybhaocyXrwgI
pkce cookie exists in getAuthorizationUrl
, but the value is different yet again?
🔐 ~ @auth / actions / getAuthorizationUrl / pkce cookie {
name: 'next-auth.pkce.code_verifier',
value: 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwia2lkIjoienJlMGlVeGZZcnpVSGI1Z3dzUlRONDM3MHR6bk9jVkFQZWt4a3JQMmZWa1ZsVGtZUENWMG4ydE1hVmozcXpRWHhVbl80TERDdjZSeXlVWmV3NEZYRncifQ..ZhAOB0nor1lRcMFbEuTYVQ.seIqoaqyIOk1svcTyvvgVxJvv9gUCT8txRPcDv14Ea7x99hEpBSPmD0seui1m_zl4w52Tr5WhItYq-lvFF62A9dukHLZfyq78BbK9QMWBTaL48mcm_f4feDFPS-oXLRTRam7KnyIeBfhJhFjMubP5h9YtMuGmYvGK1jR8irhf76dxrkWp9aN4nSlwQ1_lDsX.r5IzWQsnDn66qsKlwJx6kdkvQjdunjrn6EsOnZTBXvI',
options: {
httpOnly: true,
sameSite: 'none',
path: '/',
secure: true,
maxAge: 900,
expires: 2024-08-20T21:23:28.056Z
}
}
in the code challenge, the value matches one of the previous pkce values
🔐 ~ @auth / actions / getAuthorizationUrl / code_challenge MCROqQtkYxoQoxKNmm61tce9CdyqhDjybhaocyXrwgI
🔈 ~ @auth / actions / getAuthorizationUrl / authParams: URLSearchParams {
'response_type' => 'code',
'client_id' => 'frontend-standard-flow-app',
'redirect_uri' => 'https://sso.yyy.xxx.com/api/auth/callback/yyy-sso-xxx',
'kc_idp_hint' => 'eas',
'identity_provider' => 'eas',
'idp_hint' => 'eas',
'code_challenge' => 'MCROqQtkYxoQoxKNmm61tce9CdyqhDjybhaocyXrwgI',
'code_challenge_method' => 'S256' }
authorization url is ready with pkce cookie
[90m[auth][debug]:[0m authorization url is ready {
"url": "https://sso.xxx.com/auth/realms/yyy/protocol/openid-connect/auth?response_type=code&client_id=frontend-standard-flow-app&redirect_uri=https%3A%2F%2Fsso.yyy.xxx.com%2Fapi%2Fauth%2Fcallback%2Frogers-sso-colonynetworks&kc_idp_hint=eas&identity_provider=eas&idp_hint=eas&code_challenge=MCROqQtkYxoQoxKNmm61tce9CdyqhDjybhaocyXrwgI&code_challenge_method=S256&scope=openid+profile+email",
"cookies": [
{
"name": "next-auth.pkce.code_verifier",
"value": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwia2lkIjoienJlMGlVeGZZcnpVSGI1Z3dzUlRONDM3MHR6bk9jVkFQZWt4a3JQMmZWa1ZsVGtZUENWMG4ydE1hVmozcXpRWHhVbl80TERDdjZSeXlVWmV3NEZYRncifQ..ZhAOB0nor1lRcMFbEuTYVQ.seIqoaqyIOk1svcTyvvgVxJvv9gUCT8txRPcDv14Ea7x99hEpBSPmD0seui1m_zl4w52Tr5WhItYq-lvFF62A9dukHLZfyq78BbK9QMWBTaL48mcm_f4feDFPS-oXLRTRam7KnyIeBfhJhFjMubP5h9YtMuGmYvGK1jR8irhf76dxrkWp9aN4nSlwQ1_lDsX.r5IzWQsnDn66qsKlwJx6kdkvQjdunjrn6EsOnZTBXvI",
"options": {
"httpOnly": true,
"sameSite": "none",
"path": "/",
"secure": true,
"maxAge": 900,
"expires": "2024-08-20T21:23:28.056Z"
}
}
],
"provider": {
"id": "yyy-sso-xxx",
"name": "Keycloak",
"type": "oidc",
"style": {
"brandColor": "#428bca"
},
"clientId": "frontend-standard-flow-app",
"clientSecret": "REQUIRED_BY_NEXT_AUTH_BUT_UNUSED",
"issuer": "https://sso.xxx.com/auth/realms/yyy",
"signinUrl": "https://sso.yyy.xxx.com/api/auth/signin/yyy-sso-xxx",
"callbackUrl": "https://sso.yyy.xxx.com/api/auth/callback/yyy-sso-xxx",
"wellKnown": "https://sso.xxx.com/auth/realms/rogers/.well-known/openid-configuration",
"checks": [
"pkce"
]
}
}
signIn url seems correct
🔈 ~ next-auth / signIn / url: https://sso.yyy.xxx.com/api/auth/signin/yyy-sso-xxx?kc_idp_hint=eas&identity_provider=eas&idp_hint=eas
callbackUrl is the 3rd party oidc login website, which seems right.
🔈 ~ next-auth / signIn / body: URLSearchParams { 'callbackUrl' => 'https://zzz.yyy.com/' }
@auth/core internalRequest.url
🔈 ~ @auth / Auth / internalRequest.url: URL {
href: 'https://sso.yyy.xxx.com/api/auth/callback/yyy-sso-xxx?session_state=1bb44980-30db-4859-8ea1-e1b0d8b097fb&iss=https%3A%2F%2Fsso.xxx.com%2Fauth%2Frealms%2Fyyy&code=5ef5cc45-d8bc-49d5-899f-5d07dcd3fd07.1bb44980-30db-4859-8ea1-e1b0d8b097fb.445c02ab-27e4-4dde-93ca-31c6b8f3422f',
origin: 'https://sso.yyy.xxx.com/',
protocol: 'https:',
username: '',
password: '',
host: 'sso.yyy.xxx.com',
hostname: 'sso.yyy.xxx.com',
port: '',
pathname: '/api/auth/callback/yyy-sso-colonynetworks',
search: '?session_state=1bb44980-30db-4859-8ea1-e1b0d8b097fb&iss=https%3A%2F%2Fsso.xxx.com%2Fauth%2Frealms%2Fyyy&code=5ef5cc45-d8bc-49d5-899f-5d07dcd3fd07.1bb44980-30db-4859-8ea1-e1b0d8b097fb.445c02ab-27e4-4dde-93ca-31c6b8f3422f',
searchParams: URLSearchParams {
'session_state' => '1bb44980-30db-4859-8ea1-e1b0d8b097fb',
'iss' => 'https://sso.xxx.com/auth/realms/yyy',
'code' => '5ef5cc45-d8bc-49d5-899f-5d07dcd3fd07.1bb44980-30db-4859-8ea1-e1b0d8b097fb.445c02ab-27e4-4dde-93ca-31c6b8f3422f' },
hash: ''
}
in @auth/core oauth checks file, the pkce cookies is empty 😞, this is the cause of the error, but the problem is that it shouldn't be empty.
🛡️ ~ @auth / oauth / checks / pkce / cookies: {}
resCookies are also empty
🛡️ ~ @auth / oauth / checks / pkce / resCookies: []
cookie options
🛡️ ~ @auth / oauth / checks / pkce / options.cookies: {
sessionToken: {
name: '__Secure-authjs.session-token',
options: { httpOnly: true, sameSite: 'lax', path: '/', secure: true }
},
callbackUrl: {
name: '__Secure-authjs.callback-url',
options: { httpOnly: true, sameSite: 'lax', path: '/', secure: true }
},
csrfToken: {
name: '__Host-authjs.csrf-token',
options: { httpOnly: true, sameSite: 'lax', path: '/', secure: true }
},
pkceCodeVerifier: {
name: 'next-auth.pkce.code_verifier',
options: {
httpOnly: true,
sameSite: 'none',
path: '/',
secure: true,
maxAge: 900
}
},
state: {
name: '__Secure-authjs.state',
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: true,
maxAge: 900
}
},
nonce: {
name: '__Secure-authjs.nonce',
options: { httpOnly: true, sameSite: 'lax', path: '/', secure: true }
},
webauthnChallenge: {
name: '__Secure-authjs.challenge',
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: true,
maxAge: 900
}
}
}
the pkce codeVerifier is undefined
which is what causes the final error.
🛡️ ~ @auth / oauth / checks / pkce / codeVerifier: undefined
[31m[auth][error][0m InvalidCheck: PKCE code_verifier cookie was missing.. Read more at [https://errors.authjs.dev#invalidcheck](https://errors.authjs.dev/#invalidcheck)
Interestingly, and possibly another bug?
If I try the same login in incognito I get a different error:
[31m[auth][error][0m UnknownAction: Unsupported action. Read more at [https://errors.authjs.dev#unknownaction](https://errors.authjs.dev/#unknownaction)
I'm not writing or calling any actions myself besides the signIn
function from next-auth. So I'm not sure how/why that's happening. But only happens in incognito mode.
Today I attempted to downgrade to next-auth@4
to see if that worked, or at least provided more information.
Downgrading to next-auth@4
did resolve the PKCE code_verifier cookie was missing..
error. However it's still not functional with the oidc login through a 3rd party.
New behavior:
/auth/signin
(a route where I call the signIn
function client-side from next-auth)next-auth
redirects the user to the keycloak login page. (The user is already logged in and have an active session with keycloak for this realm, the user should never be redirected to the login page, and the users here don't even have a direct login for the keycloak page)@balazsorban44 Any idea why this happens?
@MarkLyck in the browser, do you see the cookies being set successfully before the redirection to Keycloak? (Using next-auth@beta
)
@ThangHuuVu Thank you for the response! And sorry the reply is a bit late, we can only deploy on Tuesdays and Thursdays to test this.
Here are the browser cookies that get set for me with next-auth@beta (v5)
This screenshot is taken from the /api/auth/error?error=Configuration
route after the PKCE missing cookie error is shown.
@ThangHuuVu @balazsorban44 Hmmm I believe we might have figured out the reason for this.
For the affected clients, we have an HTTP proxy that opens a new HTTP connection to Vercel but still preserving the URL hostname so Vercel knows who it is. This is required due to their custom SSL certificates.
So when next-auth used the VERCEL domain name ENV variable, it's actually using the wrong domain name for the authentication.
Sadly we cannot use the AUTH_URL
environment variable either, since we have multi-tenancy and different clients have different domains all pointing to the same Vercel deployment.
Is there any way to dynamically set the auth URL? It would be easy if we could just set it in the config. But I didn't see this in the docs.
I'm going to attempt dynamically setting the redirectProxyUrl
to the actual hostname, instead of the domain Vercel thinks the user is on with our next deployment.
@ThangHuuVu @balazsorban44 The above attempt failed.
I tried both setting the redirectProxyUrl
to the real domain, and setting redirectTo
to the https://realdomain.com/api/auth
But the user is still getting redirected back to the Vercel domain after it authenticates with Keycloak 😞
Found a thread with a similar looking issue: https://github.com/nextauthjs/next-auth/issues/10928
I tried implementing the suggest workaround, and that does make next-auth redirect to the correct URL. But the URLs used in the internal requests within next-auth, including getAuthorizationUrl
which I think is used for the PKCE code verifier cookie, is still the incorrect VERCEL domain URL.
// In src/app/api/[...nextauth]/route.ts
import { handlers } from "@/auth"
import { NextRequest } from "next/server"
const reqWithTrustedOrigin = (req: NextRequest): NextRequest => {
if (process.env.AUTH_TRUST_HOST !== 'true') return req
const proto = req.headers.get('x-forwarded-proto')
const host = req.headers.get('x-forwarded-host')
if (!proto || !host) {
console.warn("Missing x-forwarded-proto or x-forwarded-host headers.")
return req
}
const envOrigin = `${proto}://${host}`
const { href, origin } = req.nextUrl
return new NextRequest(href.replace(origin, envOrigin), req)
}
export const GET = (req: NextRequest) => {
return handlers.GET(reqWithTrustedOrigin(req))
}
export const POST = (req: NextRequest) => {
return handlers.POST(reqWithTrustedOrigin(req))
}
So while the URLs are not correct in the browser, the PKCE code verifier cookie missing error still remains.
@ThangHuuVu @balazsorban44 any other ideas we can try to get this working with next-auth? I'm pretty sure the issue is that next-auth uses the wrong URL based on the Vercel environment variable, but I don't see any way to dynamically override this for multi tenancy sites.
I managed to do something similar:
To get the auth redirect working, I removed NEXTAUTH_URL
etc and explicitly set a different one, PRODUCTION_URL
to the root URL of the "stable" production deployment. This URL is configured as an allowed callback URL in the OIDC provider (cognito, in my case).
I set redirectProxyUrl
on all deployments (master/preview):
redirectProxyUrl: process.env.PRODUCTION_URL + '/api/auth',
And then as part of the configuration:
providers: [
{
id: `cognito`,
name: `cognito`,
type: 'oidc',
clientId: COGNITO_CLIENT_ID,
clientSecret: COGNITO_CLIENT_SECRET,
issuer: COGNITO_ISSUER,
authorization: {
url: `${COGNITO_DOMAIN}/oauth2/authorize`,
params: {
response_type: 'code',
client_id: COGNITO_CLIENT_ID,
// NOTE used here
redirect_uri: `${process.env.PRODUCTION_URL}/api/auth/callback/cognito`,
},
},
token: {
url: `${COGNITO_DOMAIN}/oauth2/token`,
},
userinfo: {
url: `${COGNITO_DOMAIN}/oauth2/userInfo`,
},
This appears to work just fine when using signIn('cognito')
in the client - no additional params required.
The key difference from what i understood the documentation to infer was that all deployments need the redirectProxyUrl
set, or it is trying to "consume" your login request on the main deployment instead of forwarding it to the callbackUrl provided in the state
parameter.
I understand this is not exactly your use case but i do hope it helps.
@jack828 Thanks for the comment Jack. We did try using the redirectProxyUrl
but it didn't work in our case. The URL which does the handshake with keycloak is still using the incorrect Vercel domain instead of the domain the user is authenticated for and actually viewing the website from.
We're still stuck on this. I've pretty much given up trying to get next-auth working, and attempting to get it working with the keycloak-js
library and handling the server-side ourselves.
cc @ThangHuuVu @balazsorban44 Would still really love some input here if you have time 😞 if only it was possible to override the URL next-auth uses in the config or provider config, I believe it would work.
@jack828 Thanks for the comment Jack. We did try using the
redirectProxyUrl
but it didn't work in our case. The URL which does the handshake with keycloak is still using the incorrect Vercel domain instead of the domain the user is authenticated for and actually viewing the website from.We're still stuck on this. I've pretty much given up trying to get next-auth working, and attempting to get it working with the
keycloak-js
library and handling the server-side ourselves.cc @ThangHuuVu @balazsorban44 Would still really love some input here if you have time 😞 if only it was possible to override the URL next-auth uses in the config or provider config, I believe it would work.
@MarkLyck If you come up with an app router solution using keycloak-js, I'd appreciate a gist or something !! I'm also stuck on this.
@dclark27 We're still struggling with this as well. Likewise, if you find a solution please post it. 🙏
We did a test with keycloak-js today, but that also failed, and redirected the user to the keycloak login screen when they should already be authenticated via OIDC.
I've been working on this for a few weeks as well, if anybody has made any progress with either next-auth or keycloak-js, I would love to learn more about it.
Environment
Reproduction URL
https://github.com/MarkLyck/keycloak-pkce-error-reproduction
Describe the issue
I have been stuck on this error for 2 weeks, and could really use some help.
I'm using next-auth@5.0.0-beta.18, with multiple Keycloak providers.
The "main" keycloak provider with direct login works fine both on localhost, and when the app is deployed on Vercel. However we also have two keycloak providers with OIDC logins, which only exists in production. When I deploy my app to production, and attempt to login from these two different sites through Keycloak I get the error:
Before this error happens, it looks like the PKCECODEVERIFIER is created succesfully:
After the users attempts to login the above error happens and they are redirected to /api/auth/error?error=Configuration With a message that says: "There is a problem with the server configuration. Check the server logs for more information".
auth config;
signIn function with idp_hint:
How to reproduce
I created a minimal reproduction, but it requires setting up a 3rd party OIDC system to reproduce the issue, so it's no small task. 😞
eas
as the idp_hintI'm replacing a client-side keycloak system with next-auth. Everything besides adding next-auth is the exact same, and my 3rd party OIDC login through keycloak works fine in current production, but broken when I deploy next-auth
Expected behavior
User should be logged in and redirect to
/