Skip to content

Commit 6ec5f08

Browse files
authored
feat(backend): Warn if azp is missing on a cookie-based session token (#7929)
1 parent 44d0e5c commit 6ec5f08

3 files changed

Lines changed: 151 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/backend': patch
3+
---
4+
5+
Warn when a cookie-based session token is missing the `azp` claim instead of rejecting the token. This prepares consumers for a future version where the `azp` claim will be required.
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { describe, expect, test, vi } from 'vitest';
2+
3+
import { decodeJwt } from '../../jwt/verifyJwt';
4+
import { authenticateRequest } from '../request';
5+
import { verifyToken } from '../verify';
6+
7+
vi.mock('../verify', () => ({
8+
verifyToken: vi.fn(),
9+
verifyMachineAuthToken: vi.fn(),
10+
}));
11+
12+
vi.mock('../../jwt/verifyJwt', () => ({
13+
decodeJwt: vi.fn(),
14+
}));
15+
16+
describe('authenticateRequest with cookie token', () => {
17+
test('logs a warning when azp claim is missing but still returns signed-in', async () => {
18+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
19+
20+
const payload = {
21+
sub: 'user_123',
22+
sid: 'sess_123',
23+
iat: 1234567891,
24+
exp: 1234567991,
25+
// azp is missing
26+
};
27+
28+
vi.mocked(verifyToken).mockResolvedValue({
29+
data: payload as any,
30+
errors: undefined,
31+
});
32+
33+
vi.mocked(decodeJwt).mockReturnValue({
34+
data: { payload } as any,
35+
errors: undefined,
36+
});
37+
38+
const request = new Request('http://localhost:3000', {
39+
headers: {
40+
cookie: '__session=mock_token; __client_uat=1234567890',
41+
},
42+
});
43+
44+
const options = {
45+
publishableKey: 'pk_live_Y2xlcmsuaW5zcGlyZWQucHVtYS03NC5sY2wuZGV2JA',
46+
secretKey: 'sk_live_deadbeef',
47+
};
48+
49+
const result = await authenticateRequest(request, options);
50+
51+
expect(result.isSignedIn).toBe(true);
52+
expect(warnSpy).toHaveBeenCalledWith(
53+
'Clerk: Session token from cookie is missing the azp claim. In a future version of Clerk, this token will be considered invalid. Please contact Clerk support if you see this warning.',
54+
);
55+
56+
warnSpy.mockRestore();
57+
});
58+
59+
test('does not warn when azp claim is present', async () => {
60+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
61+
62+
const payload = {
63+
sub: 'user_123',
64+
sid: 'sess_123',
65+
iat: 1234567891,
66+
exp: 1234567991,
67+
azp: 'http://localhost:3000',
68+
};
69+
70+
vi.mocked(verifyToken).mockResolvedValue({
71+
data: payload as any,
72+
errors: undefined,
73+
});
74+
75+
vi.mocked(decodeJwt).mockReturnValue({
76+
data: { payload } as any,
77+
errors: undefined,
78+
});
79+
80+
const request = new Request('http://localhost:3000', {
81+
headers: {
82+
cookie: '__session=mock_token; __client_uat=1234567890',
83+
},
84+
});
85+
86+
const options = {
87+
publishableKey: 'pk_live_Y2xlcmsuaW5zcGlyZWQucHVtYS03NC5sY2wuZGV2JA',
88+
secretKey: 'sk_live_deadbeef',
89+
};
90+
91+
const result = await authenticateRequest(request, options);
92+
93+
expect(result.isSignedIn).toBe(true);
94+
expect(warnSpy).not.toHaveBeenCalled();
95+
96+
warnSpy.mockRestore();
97+
});
98+
});
99+
100+
describe('authenticateRequest with header token', () => {
101+
test('succeeds without warning when azp claim is missing', async () => {
102+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
103+
104+
const payload = {
105+
sub: 'user_123',
106+
sid: 'sess_123',
107+
iat: 1234567891,
108+
exp: 1234567991,
109+
// azp is missing
110+
};
111+
112+
vi.mocked(verifyToken).mockResolvedValue({
113+
data: payload as any,
114+
errors: undefined,
115+
});
116+
117+
vi.mocked(decodeJwt).mockReturnValue({
118+
data: { payload } as any,
119+
errors: undefined,
120+
});
121+
122+
const request = new Request('http://localhost:3000', {
123+
headers: {
124+
authorization: 'Bearer mock_token',
125+
},
126+
});
127+
128+
const options = {
129+
publishableKey: 'pk_live_Y2xlcmsuaW5zcGlyZWQucHVtYS03NC5sY2wuZGV2JA',
130+
secretKey: 'sk_live_deadbeef',
131+
};
132+
133+
const result = await authenticateRequest(request, options);
134+
135+
expect(result.isSignedIn).toBe(true);
136+
expect(warnSpy).not.toHaveBeenCalled();
137+
138+
warnSpy.mockRestore();
139+
});
140+
});

packages/backend/src/tokens/request.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,12 @@ export const authenticateRequest: AuthenticateRequest = (async (
632632
throw errors[0];
633633
}
634634

635+
if (!data.azp) {
636+
console.warn(
637+
'Clerk: Session token from cookie is missing the azp claim. In a future version of Clerk, this token will be considered invalid. Please contact Clerk support if you see this warning.',
638+
);
639+
}
640+
635641
const signedInRequestState = signedIn({
636642
tokenType: TokenType.SessionToken,
637643
authenticateContext,

0 commit comments

Comments
 (0)