From 638fc7db4e044edd70d0542f1d11447a27e2039f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFs=20Postula?= Date: Thu, 18 Jun 2026 21:29:43 +0200 Subject: [PATCH] feat(oauth): add OAUTH_AUTHORIZE_URL to override authorize endpoint Lets a per-brand authorize host front a single canonical issuer, so the IdP token's `iss` stays constant for downstream validation while login branding varies per domain. Discovery, token exchange and refresh keep using OAUTH_ISSUER_URL. --- .env.example | 5 +++++ app/api/auth/sso/start/route.ts | 9 +++++++-- lib/admin/types.ts | 5 +++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index b19dd622..6036141b 100644 --- a/.env.example +++ b/.env.example @@ -49,6 +49,11 @@ JMAP_SERVER_URL=https://your-jmap-server.com # OpenID Connect issuer URL for discovery # OAUTH_ISSUER_URL=https://your-idp.example.com +# Overrides only the user-facing authorize endpoint (e.g. a per-brand login +# host). Discovery, token exchange and refresh keep using OAUTH_ISSUER_URL. +# Leave unset to use the authorization_endpoint from discovery. +# OAUTH_AUTHORIZE_URL=https://login.your-brand.example.com/application/o/authorize/ + # Allow OAuth discovery to resolve to private (RFC-1918 / loopback) addresses. # Off by default as an SSRF guard. Enable for split-DNS deployments where the # OAuth issuer's public hostname resolves to an internal IP from this server. diff --git a/app/api/auth/sso/start/route.ts b/app/api/auth/sso/start/route.ts index 896e1eb0..f7978c54 100644 --- a/app/api/auth/sso/start/route.ts +++ b/app/api/auth/sso/start/route.ts @@ -8,6 +8,7 @@ import { discoverOAuth } from '@/lib/oauth/discovery'; import { getOauthScopes } from '@/lib/oauth/tokens'; import { getCookieOptions } from '@/lib/oauth/cookie-config'; import { hasSessionSecret } from '@/lib/auth/session-secret'; +import { configManager } from '@/lib/admin/config-manager'; const SSO_PENDING_COOKIE = 'sso_pending'; const SSO_PENDING_MAX_AGE = 300; // 5 minutes @@ -102,8 +103,12 @@ export async function POST(request: NextRequest) { maxAge: SSO_PENDING_MAX_AGE, }); - // Build authorize URL - const authUrl = new URL(metadata.authorization_endpoint); + // Build authorize URL. OAUTH_AUTHORIZE_URL, when set, overrides only the + // user-facing authorize endpoint (e.g. a per-brand login host). Discovery, + // token exchange and refresh keep using the canonical discovered endpoints. + const authorizeOverride = + configManager.get('oauthAuthorizeUrl', '') || process.env.OAUTH_AUTHORIZE_URL; + const authUrl = new URL(authorizeOverride?.trim() || metadata.authorization_endpoint); authUrl.searchParams.set('response_type', 'code'); authUrl.searchParams.set('client_id', clientId); authUrl.searchParams.set('redirect_uri', redirect_uri); diff --git a/lib/admin/types.ts b/lib/admin/types.ts index d5bcd99a..42bae523 100644 --- a/lib/admin/types.ts +++ b/lib/admin/types.ts @@ -163,6 +163,11 @@ export const CONFIG_ENV_MAP: Record