Skip to content

Commit b7926d1

Browse files
committed
error if azp is missing on a cookie-based token
1 parent 8aae4fd commit b7926d1

File tree

3 files changed

+143
-0
lines changed

3 files changed

+143
-0
lines changed

packages/backend/src/errors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const TokenVerificationErrorReason = {
1111
TokenInvalid: 'token-invalid',
1212
TokenInvalidAlgorithm: 'token-invalid-algorithm',
1313
TokenInvalidAuthorizedParties: 'token-invalid-authorized-parties',
14+
TokenMissingAzp: 'token-missing-azp',
1415
TokenInvalidSignature: 'token-invalid-signature',
1516
TokenNotActiveYet: 'token-not-active-yet',
1617
TokenIatInTheFuture: 'token-iat-in-the-future',
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { describe, expect, test, vi } from 'vitest';
2+
3+
import { TokenVerificationErrorReason } from '../../errors';
4+
import { decodeJwt } from '../../jwt/verifyJwt';
5+
import { authenticateRequest } from '../request';
6+
import { TokenType } from '../tokenTypes';
7+
import { verifyToken } from '../verify';
8+
9+
vi.mock('../verify', () => ({
10+
verifyToken: vi.fn(),
11+
verifyMachineAuthToken: vi.fn(),
12+
}));
13+
14+
vi.mock('../../jwt/verifyJwt', () => ({
15+
decodeJwt: vi.fn(),
16+
}));
17+
18+
describe('authenticateRequest with cookie token', () => {
19+
test('throws TokenMissingAzp when azp claim is missing', async () => {
20+
const payload = {
21+
sub: 'user_123',
22+
sid: 'sess_123',
23+
iat: 1234567891,
24+
exp: 1234567991,
25+
// azp is missing
26+
};
27+
28+
// Mock verifyToken to return a payload without azp
29+
vi.mocked(verifyToken).mockResolvedValue({
30+
data: payload as any,
31+
errors: undefined,
32+
});
33+
34+
// Mock decodeJwt to return the same payload
35+
vi.mocked(decodeJwt).mockReturnValue({
36+
data: { payload } as any,
37+
errors: undefined,
38+
});
39+
40+
const request = new Request('http://localhost:3000', {
41+
headers: {
42+
cookie: '__session=mock_token; __client_uat=1234567890',
43+
},
44+
});
45+
46+
const options = {
47+
publishableKey: 'pk_live_Y2xlcmsuaW5zcGlyZWQucHVtYS03NC5sY2wuZGV2JA',
48+
secretKey: 'sk_live_deadbeef',
49+
};
50+
51+
const result = await authenticateRequest(request, options);
52+
53+
expect(result.status).toBe('signed-out');
54+
// @ts-ignore
55+
expect(result.reason).toBe(TokenVerificationErrorReason.TokenMissingAzp);
56+
// @ts-ignore
57+
expect(result.message).toBe(
58+
'Session tokens from cookies must have an azp claim. (reason=token-missing-azp, token-carrier=cookie)',
59+
);
60+
});
61+
62+
test('succeeds when azp claim is present', async () => {
63+
const payload = {
64+
sub: 'user_123',
65+
sid: 'sess_123',
66+
iat: 1234567891,
67+
exp: 1234567991,
68+
azp: 'http://localhost:3000',
69+
};
70+
71+
// Mock verifyToken to return a payload with azp
72+
vi.mocked(verifyToken).mockResolvedValue({
73+
data: payload as any,
74+
errors: undefined,
75+
});
76+
77+
// Mock decodeJwt to return the same payload
78+
vi.mocked(decodeJwt).mockReturnValue({
79+
data: { payload } as any,
80+
errors: undefined,
81+
});
82+
83+
const request = new Request('http://localhost:3000', {
84+
headers: {
85+
cookie: '__session=mock_token; __client_uat=1234567890',
86+
},
87+
});
88+
89+
const options = {
90+
publishableKey: 'pk_live_Y2xlcmsuaW5zcGlyZWQucHVtYS03NC5sY2wuZGV2JA',
91+
secretKey: 'sk_live_deadbeef',
92+
};
93+
94+
const result = await authenticateRequest(request, options);
95+
expect(result.isSignedIn).toBe(true);
96+
});
97+
});
98+
99+
describe('authenticateRequest with header token', () => {
100+
test('succeeds when azp claim is missing', async () => {
101+
const payload = {
102+
sub: 'user_123',
103+
sid: 'sess_123',
104+
iat: 1234567891,
105+
exp: 1234567991,
106+
// azp is missing
107+
};
108+
109+
// Mock verifyToken to return a payload without azp
110+
vi.mocked(verifyToken).mockResolvedValue({
111+
data: payload as any,
112+
errors: undefined,
113+
});
114+
115+
// Mock decodeJwt to return the same payload
116+
vi.mocked(decodeJwt).mockReturnValue({
117+
data: { payload } as any,
118+
errors: undefined,
119+
});
120+
121+
const request = new Request('http://localhost:3000', {
122+
headers: {
123+
authorization: 'Bearer mock_token',
124+
},
125+
});
126+
127+
const options = {
128+
publishableKey: 'pk_live_Y2xlcmsuaW5zcGlyZWQucHVtYS03NC5sY2wuZGV2JA',
129+
secretKey: 'sk_live_deadbeef',
130+
};
131+
132+
const result = await authenticateRequest(request, options);
133+
expect(result.isSignedIn).toBe(true);
134+
});
135+
});

packages/backend/src/tokens/request.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,13 @@ export const authenticateRequest: AuthenticateRequest = (async (
565565
throw errors[0];
566566
}
567567

568+
if (!data.azp) {
569+
throw new TokenVerificationError({
570+
reason: TokenVerificationErrorReason.TokenMissingAzp,
571+
message: 'Session tokens from cookies must have an azp claim.',
572+
});
573+
}
574+
568575
const signedInRequestState = signedIn({
569576
tokenType: TokenType.SessionToken,
570577
authenticateContext,

0 commit comments

Comments
 (0)