diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index f8bebc1ff00..80ab7c50761 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -456,6 +456,10 @@ services: environment: # Legacy symmetric HS256 key JWT_SECRET: ${JWT_SECRET} + # JWKS for token verification (EC public + legacy symmetric). + # For Podman, use: SUPABASE_JWKS: ${JWT_JWKS} + #SUPABASE_JWKS: ${JWT_JWKS:-{"keys":[]}} + SUPABASE_URL: http://kong:8000 SUPABASE_PUBLIC_URL: ${SUPABASE_PUBLIC_URL} # Legacy API keys (HS256-signed JWTs) diff --git a/docker/utils/add-new-auth-keys.sh b/docker/utils/add-new-auth-keys.sh index b728237b7c0..3073ca76365 100644 --- a/docker/utils/add-new-auth-keys.sh +++ b/docker/utils/add-new-auth-keys.sh @@ -168,6 +168,7 @@ echo "" echo " Auth: GOTRUE_JWT_KEYS: \${JWT_KEYS:-[]}" echo " Realtime: API_JWT_JWKS: \${JWT_JWKS:-{\"keys\":[]}}" echo " Storage: JWT_JWKS: \${JWT_JWKS:-{\"keys\":[]}}" +echo " Functions: SUPABASE_JWKS: \${JWT_JWKS:-{\"keys\":[]}}" echo "" if [ "$1" = "--update-env" ]; then @@ -212,11 +213,13 @@ sed -i.old \ -e '/^[ ]*#GOTRUE_JWT_KEYS:/ s/#//' \ -e '/^[ ]*#API_JWT_JWKS:/ s/#//' \ -e '/^[ ]*#JWT_JWKS:/ s/#//' \ + -e '/^[ ]*#SUPABASE_JWKS:/ s/#//' \ docker-compose.yml || true if grep -q '^[ ]*GOTRUE_JWT_KEYS:' docker-compose.yml && \ grep -q '^[ ]*API_JWT_JWKS:' docker-compose.yml && \ - grep -q '^[ ]*JWT_JWKS:' docker-compose.yml; then + grep -q '^[ ]*JWT_JWKS:' docker-compose.yml && \ + grep -q '^[ ]*SUPABASE_JWKS:' docker-compose.yml; then echo "Done." else echo "Warning: could not edit docker-compose.yml. Uncomment auth configuration manually." diff --git a/docker/volumes/functions/main/index.ts b/docker/volumes/functions/main/index.ts index ebe2061c4ba..4761a276657 100644 --- a/docker/volumes/functions/main/index.ts +++ b/docker/volumes/functions/main/index.ts @@ -1,29 +1,32 @@ -import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts' +import * as jose from 'jsr:@panva/jose@6' console.log('main function started') const JWT_SECRET = Deno.env.get('JWT_SECRET') -const SUPABASE_URL = Deno.env.get('SUPABASE_URL') +const SUPABASE_JWKS = parseJwks(Deno.env.get('SUPABASE_JWKS')) const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true' -// Create JWKS for ES256/RS256 tokens (newer tokens) -let SUPABASE_JWT_KEYS: ReturnType | null = null -if (SUPABASE_URL) { +// NOTE:(kallebysantos) We don't check for valid keys but just the bare array parsing, +// let this for 'jose' lib verification +export function parseJwks(raw: string | undefined): jose.JSONWebKeySet | null { + if (!raw) return null try { - SUPABASE_JWT_KEYS = jose.createRemoteJWKSet( - new URL('/auth/v1/.well-known/jwks.json', SUPABASE_URL) - ) - } catch (e) { - console.error('Failed to fetch JWKS from SUPABASE_URL:', e) + const parsed = JSON.parse(raw) + if (parsed?.keys && Array.isArray(parsed.keys)) { + return parsed as jose.JSONWebKeySet + } + return null + } catch { + return null } } /** * Extract JWT token from Authorization header - * + * * Parses the Authorization header to extract the Bearer token. * Expects format: "Bearer " - * + * * @param req - The HTTP request object * @returns The JWT token string * @throws Error if Authorization header is missing or malformed @@ -47,7 +50,7 @@ async function isValidLegacyJWT(jwt: string): Promise { } const encoder = new TextEncoder(); - const secretKey = encoder.encode(JWT_SECRET) + const secretKey = encoder.encode(JWT_SECRET); try { await jose.jwtVerify(jwt, secretKey); @@ -59,13 +62,14 @@ async function isValidLegacyJWT(jwt: string): Promise { } async function isValidJWT(jwt: string): Promise { - if (!SUPABASE_JWT_KEYS) { + if (!SUPABASE_JWKS) { console.error('JWKS not available for ES256/RS256 token verification') return false } try { - await jose.jwtVerify(jwt, SUPABASE_JWT_KEYS) + const localJwks = jose.createLocalJWKSet(SUPABASE_JWKS); + await jose.jwtVerify(jwt, localJwks); } catch (e) { console.error('Asymmetric JWT verification error', e); return false