From 41cabebc65dfd12afec31a6c6e5f98eb78738286 Mon Sep 17 00:00:00 2001 From: SumitKumar-17 Date: Sat, 25 Oct 2025 02:33:57 +0530 Subject: [PATCH 1/2] feat(auth): add TypeScript types for documented JWT claims fields - Added type safety for email, phone, user_metadata, app_metadata, is_anonymous, etc. - Provides autocomplete and type safety for all documented JWT claims fields - getClaims() method now returns properly typed JwtPayload with documented fields Fixes #1584 --- packages/core/auth-js/src/lib/types.ts | 16 +++++++++- .../core/auth-js/test/GoTrueClient.test.ts | 29 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/core/auth-js/src/lib/types.ts b/packages/core/auth-js/src/lib/types.ts index 5a2327d63..4dd354359 100644 --- a/packages/core/auth-js/src/lib/types.ts +++ b/packages/core/auth-js/src/lib/types.ts @@ -1439,7 +1439,21 @@ export type RequiredClaims = { session_id: string } -export type JwtPayload = RequiredClaims & { +export interface JwtPayload extends RequiredClaims { + email?: string + phone?: string + user_metadata?: UserMetadata + app_metadata?: UserAppMetadata + is_anonymous?: boolean + is_sso_user?: boolean + id?: string + created_at?: string + updated_at?: string + confirmed_at?: string + email_confirmed_at?: string + phone_confirmed_at?: string + last_sign_in_at?: string + identities?: UserIdentity[] [key: string]: any } diff --git a/packages/core/auth-js/test/GoTrueClient.test.ts b/packages/core/auth-js/test/GoTrueClient.test.ts index 5ed3fd4ff..1327f9696 100644 --- a/packages/core/auth-js/test/GoTrueClient.test.ts +++ b/packages/core/auth-js/test/GoTrueClient.test.ts @@ -1613,6 +1613,35 @@ describe('getClaims', () => { expect(authWithSession.getUser).toHaveBeenCalled() }) + test('getClaims returns properly typed JwtPayload with documented fields', async () => { + const { email, password } = mockUserCredentials() + const { + data: { user }, + error: initialError, + } = await authWithSession.signUp({ + email, + password, + }) + expect(initialError).toBeNull() + expect(user).not.toBeNull() + + const { data, error } = await authWithSession.getClaims() + expect(error).toBeNull() + expect(data).not.toBeNull() + + const claims = data?.claims + expect(claims).toBeDefined() + + expect(typeof claims?.email).toBe('string') + expect(typeof claims?.user_metadata).toBe('object') + expect(typeof claims?.app_metadata).toBe('object') + expect(typeof claims?.is_anonymous).toBe('boolean') + expect(typeof claims?.is_sso_user).toBe('boolean') + expect(typeof claims?.id).toBe('string') + expect(typeof claims?.created_at).toBe('string') + expect(typeof claims?.role).toBe('string') + }) + test('getClaims fetches JWKS to verify asymmetric jwt', async () => { const fetchedUrls: any[] = [] const fetchedResponse: any[] = [] From 37201b70492d49921102ad599393030e29807e22 Mon Sep 17 00:00:00 2001 From: Katerina Skroumpelou Date: Wed, 5 Nov 2025 18:08:21 +0200 Subject: [PATCH 2/2] fix(auth): correct JWT claims types to match official documentation --- packages/core/auth-js/src/lib/types.ts | 32 ++++++--- .../core/auth-js/test/GoTrueClient.test.ts | 70 ++++++++++++++++--- 2 files changed, 82 insertions(+), 20 deletions(-) diff --git a/packages/core/auth-js/src/lib/types.ts b/packages/core/auth-js/src/lib/types.ts index 4dd354359..930d994fe 100644 --- a/packages/core/auth-js/src/lib/types.ts +++ b/packages/core/auth-js/src/lib/types.ts @@ -1439,21 +1439,31 @@ export type RequiredClaims = { session_id: string } +/** + * JWT Payload containing claims for Supabase authentication tokens. + * + * Required claims (iss, aud, exp, iat, sub, role, aal, session_id) are inherited from RequiredClaims. + * All other claims are optional as they can be customized via Custom Access Token Hooks. + * + * @see https://supabase.com/docs/guides/auth/jwt-fields + */ export interface JwtPayload extends RequiredClaims { + // Standard optional claims (can be customized via custom access token hooks) email?: string phone?: string - user_metadata?: UserMetadata - app_metadata?: UserAppMetadata is_anonymous?: boolean - is_sso_user?: boolean - id?: string - created_at?: string - updated_at?: string - confirmed_at?: string - email_confirmed_at?: string - phone_confirmed_at?: string - last_sign_in_at?: string - identities?: UserIdentity[] + + // Optional claims + jti?: string + nbf?: number + app_metadata?: UserAppMetadata + user_metadata?: UserMetadata + amr?: AMREntry[] + + // Special claims (only in anon/service role tokens) + ref?: string + + // Allow custom claims via custom access token hooks [key: string]: any } diff --git a/packages/core/auth-js/test/GoTrueClient.test.ts b/packages/core/auth-js/test/GoTrueClient.test.ts index 1327f9696..d5141511a 100644 --- a/packages/core/auth-js/test/GoTrueClient.test.ts +++ b/packages/core/auth-js/test/GoTrueClient.test.ts @@ -1628,18 +1628,70 @@ describe('getClaims', () => { const { data, error } = await authWithSession.getClaims() expect(error).toBeNull() expect(data).not.toBeNull() - + const claims = data?.claims expect(claims).toBeDefined() - - expect(typeof claims?.email).toBe('string') - expect(typeof claims?.user_metadata).toBe('object') - expect(typeof claims?.app_metadata).toBe('object') - expect(typeof claims?.is_anonymous).toBe('boolean') - expect(typeof claims?.is_sso_user).toBe('boolean') - expect(typeof claims?.id).toBe('string') - expect(typeof claims?.created_at).toBe('string') + + // Test core required claims that are always present + expect(typeof claims?.sub).toBe('string') expect(typeof claims?.role).toBe('string') + + // Test standard optional claims + if (claims?.email !== undefined) { + expect(typeof claims.email).toBe('string') + } + if (claims?.phone !== undefined) { + expect(typeof claims.phone).toBe('string') + } + if (claims?.user_metadata !== undefined) { + expect(typeof claims.user_metadata).toBe('object') + } + if (claims?.app_metadata !== undefined) { + expect(typeof claims.app_metadata).toBe('object') + } + if (claims?.is_anonymous !== undefined) { + expect(typeof claims.is_anonymous).toBe('boolean') + } + + // Test optional JWT standard claims if present + if (claims?.iss !== undefined) { + expect(typeof claims.iss).toBe('string') + } + if (claims?.aud !== undefined) { + expect(['string', 'object']).toContain(typeof claims.aud) + } + if (claims?.exp !== undefined) { + expect(typeof claims.exp).toBe('number') + } + if (claims?.iat !== undefined) { + expect(typeof claims.iat).toBe('number') + } + if (claims?.aal !== undefined) { + expect(typeof claims.aal).toBe('string') + } + if (claims?.session_id !== undefined) { + expect(typeof claims.session_id).toBe('string') + } + if (claims?.jti !== undefined) { + expect(typeof claims.jti).toBe('string') + } + if (claims?.nbf !== undefined) { + expect(typeof claims.nbf).toBe('number') + } + + // Verify amr array structure if present + if (claims?.amr) { + expect(Array.isArray(claims.amr)).toBe(true) + if (claims.amr.length > 0) { + expect(typeof claims.amr[0].method).toBe('string') + expect(typeof claims.amr[0].timestamp).toBe('number') + } + } + + // Verify ref claim if present (anon/service role tokens) + if (claims?.ref !== undefined) { + expect(typeof claims.ref).toBe('string') + } }) test('getClaims fetches JWKS to verify asymmetric jwt', async () => {