Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/reusable_exec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ jobs:
cd docker
sh utils/add-new-auth-keys.sh --update-env > /dev/null
yq -i '
.services.auth.environment.GOTRUE_JWT_KEYS="${JWT_KEYS:-[]}" |
.services.storage.environment.JWT_JWKS="${JWT_JWKS:-{\"keys\":[]}}" |
.services.realtime.environment.API_JWT_JWKS="${JWT_JWKS:-{\"keys\":[]}}"
.services.auth.environment.GOTRUE_JWT_KEYS="${JWT_KEYS:?error}" |
.services.storage.environment.JWT_JWKS="${JWT_JWKS:?error}" |
.services.realtime.environment.API_JWT_JWKS="${JWT_JWKS:?error}" |
.services.functions.environment.SUPABASE_JWKS="${JWT_JWKS:?error}"
' docker-compose.yml

- uses: actions/setup-node@v6
Expand Down
4 changes: 4 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,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)
Expand Down
34 changes: 19 additions & 15 deletions docker/volumes/functions/main/index.ts
Original file line number Diff line number Diff line change
@@ -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<typeof jose.createRemoteJWKSet> | 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 <token>"
*
*
* @param req - The HTTP request object
* @returns The JWT token string
* @throws Error if Authorization header is missing or malformed
Expand All @@ -47,7 +50,7 @@ async function isValidLegacyJWT(jwt: string): Promise<boolean> {
}

const encoder = new TextEncoder();
const secretKey = encoder.encode(JWT_SECRET)
const secretKey = encoder.encode(JWT_SECRET);

try {
await jose.jwtVerify(jwt, secretKey);
Expand All @@ -59,13 +62,14 @@ async function isValidLegacyJWT(jwt: string): Promise<boolean> {
}

async function isValidJWT(jwt: string): Promise<boolean> {
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
Expand Down
Loading