Skip to content

Commit 18dfdd6

Browse files
committed
feat: functions expose jwks
1 parent cc703a1 commit 18dfdd6

2 files changed

Lines changed: 23 additions & 15 deletions

File tree

docker/docker-compose.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,10 @@ services:
436436
environment:
437437
# Legacy symmetric HS256 key
438438
JWT_SECRET: ${JWT_SECRET}
439+
# JWKS for token verification (EC public + legacy symmetric).
440+
# For Podman, use: SUPABASE_JWKS: ${JWT_JWKS}
441+
#SUPABASE_JWKS: ${JWT_JWKS:-{"keys":[]}}
442+
439443
SUPABASE_URL: http://kong:8000
440444
SUPABASE_PUBLIC_URL: ${SUPABASE_PUBLIC_URL}
441445
# Legacy API keys (HS256-signed JWTs)

docker/volumes/functions/main/index.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
1-
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'
1+
import * as jose from 'jsr:@panva/jose@6'
22

33
console.log('main function started')
44

55
const JWT_SECRET = Deno.env.get('JWT_SECRET')
6-
const SUPABASE_URL = Deno.env.get('SUPABASE_URL')
6+
const SUPABASE_JWKS = parseJwks(Deno.env.get('SUPABASE_JWKS'))
77
const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'
88

9-
// Create JWKS for ES256/RS256 tokens (newer tokens)
10-
let SUPABASE_JWT_KEYS: ReturnType<typeof jose.createRemoteJWKSet> | null = null
11-
if (SUPABASE_URL) {
9+
// NOTE:(kallebysantos) We don't check for valid keys but just the bare array parsing,
10+
// let this for 'jose' lib verification
11+
export function parseJwks(raw: string | undefined): jose.JSONWebKeySet | null {
12+
if (!raw) return null
1213
try {
13-
SUPABASE_JWT_KEYS = jose.createRemoteJWKSet(
14-
new URL('/auth/v1/.well-known/jwks.json', SUPABASE_URL)
15-
)
16-
} catch (e) {
17-
console.error('Failed to fetch JWKS from SUPABASE_URL:', e)
14+
const parsed = JSON.parse(raw)
15+
if (parsed?.keys && Array.isArray(parsed.keys)) {
16+
return parsed as jose.JSONWebKeySet
17+
}
18+
return null
19+
} catch {
20+
return null
1821
}
1922
}
2023

2124
/**
2225
* Extract JWT token from Authorization header
23-
*
26+
*
2427
* Parses the Authorization header to extract the Bearer token.
2528
* Expects format: "Bearer <token>"
26-
*
29+
*
2730
* @param req - The HTTP request object
2831
* @returns The JWT token string
2932
* @throws Error if Authorization header is missing or malformed
@@ -47,7 +50,7 @@ async function isValidLegacyJWT(jwt: string): Promise<boolean> {
4750
}
4851

4952
const encoder = new TextEncoder();
50-
const secretKey = encoder.encode(JWT_SECRET)
53+
const secretKey = encoder.encode(JWT_SECRET);
5154

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

6164
async function isValidJWT(jwt: string): Promise<boolean> {
62-
if (!SUPABASE_JWT_KEYS) {
65+
if (!SUPABASE_JWKS) {
6366
console.error('JWKS not available for ES256/RS256 token verification')
6467
return false
6568
}
6669

6770
try {
68-
await jose.jwtVerify(jwt, SUPABASE_JWT_KEYS)
71+
const localJwks = jose.createLocalJWKSet(SUPABASE_JWKS);
72+
await jose.jwtVerify(jwt, localJwks);
6973
} catch (e) {
7074
console.error('Asymmetric JWT verification error', e);
7175
return false

0 commit comments

Comments
 (0)