Skip to content

Commit 2981692

Browse files
feat(auth): add TypeScript types for documented JWT claims fields (#1802)
Co-authored-by: Katerina Skroumpelou <[email protected]>
1 parent dd0fee1 commit 2981692

File tree

2 files changed

+106
-1
lines changed

2 files changed

+106
-1
lines changed

packages/core/auth-js/src/lib/types.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1439,7 +1439,31 @@ export type RequiredClaims = {
14391439
session_id: string
14401440
}
14411441

1442-
export type JwtPayload = RequiredClaims & {
1442+
/**
1443+
* JWT Payload containing claims for Supabase authentication tokens.
1444+
*
1445+
* Required claims (iss, aud, exp, iat, sub, role, aal, session_id) are inherited from RequiredClaims.
1446+
* All other claims are optional as they can be customized via Custom Access Token Hooks.
1447+
*
1448+
* @see https://supabase.com/docs/guides/auth/jwt-fields
1449+
*/
1450+
export interface JwtPayload extends RequiredClaims {
1451+
// Standard optional claims (can be customized via custom access token hooks)
1452+
email?: string
1453+
phone?: string
1454+
is_anonymous?: boolean
1455+
1456+
// Optional claims
1457+
jti?: string
1458+
nbf?: number
1459+
app_metadata?: UserAppMetadata
1460+
user_metadata?: UserMetadata
1461+
amr?: AMREntry[]
1462+
1463+
// Special claims (only in anon/service role tokens)
1464+
ref?: string
1465+
1466+
// Allow custom claims via custom access token hooks
14431467
[key: string]: any
14441468
}
14451469

packages/core/auth-js/test/GoTrueClient.test.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,87 @@ describe('getClaims', () => {
16131613
expect(authWithSession.getUser).toHaveBeenCalled()
16141614
})
16151615

1616+
test('getClaims returns properly typed JwtPayload with documented fields', async () => {
1617+
const { email, password } = mockUserCredentials()
1618+
const {
1619+
data: { user },
1620+
error: initialError,
1621+
} = await authWithSession.signUp({
1622+
email,
1623+
password,
1624+
})
1625+
expect(initialError).toBeNull()
1626+
expect(user).not.toBeNull()
1627+
1628+
const { data, error } = await authWithSession.getClaims()
1629+
expect(error).toBeNull()
1630+
expect(data).not.toBeNull()
1631+
1632+
const claims = data?.claims
1633+
expect(claims).toBeDefined()
1634+
1635+
// Test core required claims that are always present
1636+
expect(typeof claims?.sub).toBe('string')
1637+
expect(typeof claims?.role).toBe('string')
1638+
1639+
// Test standard optional claims
1640+
if (claims?.email !== undefined) {
1641+
expect(typeof claims.email).toBe('string')
1642+
}
1643+
if (claims?.phone !== undefined) {
1644+
expect(typeof claims.phone).toBe('string')
1645+
}
1646+
if (claims?.user_metadata !== undefined) {
1647+
expect(typeof claims.user_metadata).toBe('object')
1648+
}
1649+
if (claims?.app_metadata !== undefined) {
1650+
expect(typeof claims.app_metadata).toBe('object')
1651+
}
1652+
if (claims?.is_anonymous !== undefined) {
1653+
expect(typeof claims.is_anonymous).toBe('boolean')
1654+
}
1655+
1656+
// Test optional JWT standard claims if present
1657+
if (claims?.iss !== undefined) {
1658+
expect(typeof claims.iss).toBe('string')
1659+
}
1660+
if (claims?.aud !== undefined) {
1661+
expect(['string', 'object']).toContain(typeof claims.aud)
1662+
}
1663+
if (claims?.exp !== undefined) {
1664+
expect(typeof claims.exp).toBe('number')
1665+
}
1666+
if (claims?.iat !== undefined) {
1667+
expect(typeof claims.iat).toBe('number')
1668+
}
1669+
if (claims?.aal !== undefined) {
1670+
expect(typeof claims.aal).toBe('string')
1671+
}
1672+
if (claims?.session_id !== undefined) {
1673+
expect(typeof claims.session_id).toBe('string')
1674+
}
1675+
if (claims?.jti !== undefined) {
1676+
expect(typeof claims.jti).toBe('string')
1677+
}
1678+
if (claims?.nbf !== undefined) {
1679+
expect(typeof claims.nbf).toBe('number')
1680+
}
1681+
1682+
// Verify amr array structure if present
1683+
if (claims?.amr) {
1684+
expect(Array.isArray(claims.amr)).toBe(true)
1685+
if (claims.amr.length > 0) {
1686+
expect(typeof claims.amr[0].method).toBe('string')
1687+
expect(typeof claims.amr[0].timestamp).toBe('number')
1688+
}
1689+
}
1690+
1691+
// Verify ref claim if present (anon/service role tokens)
1692+
if (claims?.ref !== undefined) {
1693+
expect(typeof claims.ref).toBe('string')
1694+
}
1695+
})
1696+
16161697
test('getClaims fetches JWKS to verify asymmetric jwt', async () => {
16171698
const fetchedUrls: any[] = []
16181699
const fetchedResponse: any[] = []

0 commit comments

Comments
 (0)