From 0b0472f2f55d22a364bb48687fcd262473331b3b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:29:33 +0100 Subject: [PATCH] Fix OIDC token exchange behind HTTPS reverse proxy (#162) * Initial plan * Fix OIDC callback URL construction for HTTPS reverse proxy - Replace hardcoded http:// URL with OIDC_REDIRECT_URI from environment - Build complete callback URL with query parameters for proper validation - Fixes token exchange 401 errors when running behind HTTPS reverse proxy Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * Update OIDC_REDIRECT_URI documentation to clarify full URL requirement Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> * fix: format oidc.ts to pass biome check --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DanielVolz <3275994+DanielVolz@users.noreply.github.com> Co-authored-by: Daniel Volz --- README.md | 2 +- backend/package-lock.json | 6 ++++++ backend/src/routes/oidc.ts | 18 +++++++++--------- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 68368c0..46bf285 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,7 @@ Generate secrets with: `openssl rand -hex 32` | `OIDC_ISSUER_URL` | — | OIDC provider URL | | `OIDC_CLIENT_ID` | — | Client ID from OIDC provider | | `OIDC_CLIENT_SECRET` | — | Client secret from OIDC provider | -| `OIDC_REDIRECT_URI` | — | Callback URL | +| `OIDC_REDIRECT_URI` | — | Full callback URL (e.g., `https://your-domain.com/api/auth/oidc/callback`) | | `OIDC_SCOPES` | `openid profile email` | Scopes to request | | `OIDC_USERNAME_CLAIM` | `preferred_username` | Claim for username | | `OIDC_AUTO_CREATE_USERS` | `true` | Auto-create users on first SSO login | diff --git a/backend/package-lock.json b/backend/package-lock.json index b7cc723..882dae9 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -2243,6 +2243,7 @@ "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.10.0.tgz", "integrity": "sha512-2ERn08T4XOVx34yBtUPq0RDjAdd9TJ5qNH/izugr208ml2F94mk92qC64kXyDVQINodWJvp3kAdq6P4zTtCZ7g==", "license": "MIT", + "peer": true, "dependencies": { "@libsql/core": "^0.10.0", "@libsql/hrana-client": "^0.6.2", @@ -4742,6 +4743,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -5938,6 +5940,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6699,6 +6702,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -6762,6 +6766,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -6837,6 +6842,7 @@ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", diff --git a/backend/src/routes/oidc.ts b/backend/src/routes/oidc.ts index a9de5c9..a9cc089 100644 --- a/backend/src/routes/oidc.ts +++ b/backend/src/routes/oidc.ts @@ -144,17 +144,17 @@ export async function oidcRoutes(app: FastifyInstance) { try { const config = await getOIDCConfig(); - const _redirectUri = env.OIDC_REDIRECT_URI!; + const redirectUri = env.OIDC_REDIRECT_URI!; // Exchange code for tokens - const tokens = await client.authorizationCodeGrant( - config, - new URL(request.url, `http://${request.headers.host}`), - { - pkceCodeVerifier: storedVerifier.value, - expectedState: state, - } - ); + // Build complete callback URL with query parameters for validation + const callbackUrl = new URL(redirectUri); + callbackUrl.search = new URLSearchParams(request.query as Record).toString(); + + const tokens = await client.authorizationCodeGrant(config, callbackUrl, { + pkceCodeVerifier: storedVerifier.value, + expectedState: state, + }); // Get user info const sub = tokens.claims()?.sub;