Skip to content

Commit 8bfdf94

Browse files
authored
feat(backend): Signal support for handshake nonce (#5905)
1 parent 084e7cc commit 8bfdf94

File tree

4 files changed

+40
-0
lines changed

4 files changed

+40
-0
lines changed

.changeset/six-ears-wash.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
'@clerk/backend': minor
3+
---
4+
5+
## Optimize handshake payload delivery with nonce-based fetching
6+
7+
This change introduces a significant optimization to the handshake flow by replacing direct payload delivery with a nonce-based approach to overcome browser cookie size limitations.
8+
9+
## Problem Solved
10+
Previously, the handshake payload (an encoded JWT containing set-cookie headers) was sent directly in a cookie. Since browsers limit cookies to ~4KB, this severely restricted the practical size of session tokens, which are also JWTs stored in cookies but embedded within the handshake payload.
11+
12+
## Solution
13+
We now use a conditional approach based on payload size:
14+
- **Small payloads (≤2KB)**: Continue using the direct approach for optimal performance
15+
- **Large payloads (>2KB)**: Use nonce-based fetching to avoid cookie size limits
16+
17+
For large payloads, we:
18+
1. Generate a short nonce (ID) for each handshake instance
19+
2. Send only the nonce in the `__clerk_handshake_nonce` cookie
20+
3. Use the nonce to fetch the actual handshake payload via a dedicated BAPI endpoint
21+
22+
## New Handshake Flow (for payloads >2KB)
23+
1. User visits `example.com`
24+
2. Client app middleware triggers handshake → `307 FAPI/v1/client/handshake`
25+
3. FAPI handshake resolves → `307 example.com` with `__clerk_handshake_nonce` cookie containing the nonce
26+
4. Client app middleware makes `GET BAPI/v1/clients/handshake_payload?nonce=<nonce_value>` request (BAPI)
27+
5. BAPI returns array of set-cookie header values
28+
6. Client app middleware applies headers to the response
29+
30+
## Traditional Flow (for payloads ≤2KB)
31+
No changes. Continues to work as before with direct payload delivery in cookies for optimal performance.
32+
33+
## Trade-offs
34+
- **Added**: One additional BAPI call per handshake (only for payloads >2KB)
35+
- **Removed**: Cookie size restrictions that previously limited session token size
36+

packages/backend/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const QueryParameters = {
3535
LegacyDevBrowser: '__dev_session',
3636
HandshakeReason: '__clerk_hs_reason',
3737
HandshakeNonce: Cookies.HandshakeNonce,
38+
HandshakeFormat: 'format',
3839
} as const;
3940

4041
const Headers = {

packages/backend/src/tokens/__tests__/handshake.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ describe('HandshakeService', () => {
155155
expect(url.searchParams.get('redirect_url')).toBe('https://example.com/');
156156
expect(url.searchParams.get(constants.QueryParameters.SuffixedCookies)).toBe('true');
157157
expect(url.searchParams.get(constants.QueryParameters.HandshakeReason)).toBe('test-reason');
158+
expect(url.searchParams.get(constants.QueryParameters.HandshakeFormat)).toBe('nonce');
158159
});
159160

160161
it('should include dev browser token in development mode', () => {
@@ -168,6 +169,7 @@ describe('HandshakeService', () => {
168169
const url = new URL(location);
169170

170171
expect(url.searchParams.get(constants.QueryParameters.DevBrowser)).toBe('dev-token');
172+
expect(url.searchParams.get(constants.QueryParameters.HandshakeFormat)).toBe('nonce');
171173
});
172174

173175
it('should throw error if clerkUrl is missing', () => {

packages/backend/src/tokens/handshake.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export class HandshakeService {
149149
this.authenticateContext.usesSuffixedCookies().toString(),
150150
);
151151
url.searchParams.append(constants.QueryParameters.HandshakeReason, reason);
152+
url.searchParams.append(constants.QueryParameters.HandshakeFormat, 'nonce');
152153

153154
if (this.authenticateContext.instanceType === 'development' && this.authenticateContext.devBrowserToken) {
154155
url.searchParams.append(constants.QueryParameters.DevBrowser, this.authenticateContext.devBrowserToken);

0 commit comments

Comments
 (0)