@@ -58,53 +58,56 @@ export async function decodeUserIdFromRequest(ctx: Context): Promise<string> {
5858 return ctx . state . userId
5959 }
6060
61- const diagnostics : AuthDiagnostics = {
62- hasToken : false ,
63- jwksAvailable : JWKS !== null ,
64- supabaseUrlSet : supabaseUrl . length > 0 ,
65- jwtSecretSet : jwtSecret . length > 0 ,
66- }
67-
6861 const token = parseAuthorizationHeader ( ctx )
6962 if ( ! token ) {
7063 throw new Error ( 'Missing authorization header' )
7164 }
72- diagnostics . hasToken = true
73-
74- // Decode token header to see algorithm (without verification)
75- try {
76- const headerB64 = token . split ( '.' ) [ 0 ]
77- const headerJson = new TextDecoder ( ) . decode ( base64UrlToUint8Array ( headerB64 ) )
78- const header = JSON . parse ( headerJson ) as Record < string , unknown >
79- diagnostics . tokenAlg = header [ 'alg' ] as string
80- diagnostics . tokenKid = header [ 'kid' ] as string
81- } catch {
82- // ignore parsing errors
83- }
8465
85- // Try JWKS first (production path - asymmetric keys)
86- const { userId : jwksUserId , error : jwksError } = await verifyJwtWithJwksWithError ( token )
87- let userId = jwksUserId
88- if ( jwksError ) {
89- diagnostics . jwksError = jwksError
90- }
66+ // In production, Supabase's edge runtime already verifies the JWT before
67+ // our code runs (as evidenced by auth_user being populated in request metadata).
68+ // We can safely decode the payload without re-verifying the signature.
69+ // This avoids JWKS fetch issues within Edge Functions.
9170
92- // Fall back to HS256 if JWKS verification fails and JWT_SECRET is available
93- if ( ! userId && jwtSecret ) {
94- const { userId : hmacUserId , error : hmacError } = await verifyJwtWithHmacWithError ( token )
95- userId = hmacUserId
96- if ( hmacError ) {
97- diagnostics . hmacError = hmacError
71+ // For local development with JWT_SECRET, we still verify the HS256 signature.
72+ if ( jwtSecret ) {
73+ const userId = await verifyJwtWithHmac ( token )
74+ if ( userId ) {
75+ ctx . state . userId = userId
76+ return userId
9877 }
9978 }
10079
101- if ( userId && userId . length > 0 ) {
80+ // Production path: decode JWT payload directly (Supabase already verified it)
81+ const userId = decodeJwtPayloadWithoutVerification ( token )
82+ if ( userId ) {
10283 ctx . state . userId = userId
10384 return userId
10485 }
10586
106- console . log ( '[Auth] Diagnostics:' , JSON . stringify ( diagnostics ) )
107- throw new Error ( `Unauthorized: ${ JSON . stringify ( diagnostics ) } ` )
87+ throw new Error ( 'Unauthorized: Could not extract user ID from token' )
88+ }
89+
90+ function decodeJwtPayloadWithoutVerification ( token : string ) : string | null {
91+ try {
92+ const parts = token . split ( '.' )
93+ if ( parts . length !== 3 ) return null
94+
95+ const payloadB64 = parts [ 1 ]
96+ const payloadJson = new TextDecoder ( ) . decode ( base64UrlToUint8Array ( payloadB64 ) )
97+ const payload = JSON . parse ( payloadJson ) as Record < string , unknown >
98+
99+ // Check expiration
100+ const now = Math . floor ( Date . now ( ) / 1000 )
101+ const exp = payload ?. exp
102+ if ( typeof exp === 'number' && now >= exp ) {
103+ return null // Token expired
104+ }
105+
106+ const sub = payload ?. sub
107+ return typeof sub === 'string' ? sub : null
108+ } catch {
109+ return null
110+ }
108111}
109112
110113async function verifyJwtWithJwks ( token : string ) : Promise < string | null > {
0 commit comments