Skip to content

Commit 883866f

Browse files
authored
fix: reuse session ID in external storage example (#1333)
* fix: fix logto configs url * fix: reuse session ID in external storage example The previous example generated a new UUID on every wrap() call. This breaks RSC environments where cookies cannot be updated, causing session loss when the cookie still contains the old ID. Updated the MemorySessionWrapper example to store and reuse the session ID from unwrap(), ensuring the cookie value remains stable while external storage data can be updated. * docs: clarify RSC token caching limitation with solutions Expanded the tip about RSC not being able to persist refreshed tokens. Added clear solutions: using client components with Server Actions, or using external session storage with sessionWrapper.
1 parent 314bbe1 commit 883866f

File tree

2 files changed

+17
-5
lines changed

2 files changed

+17
-5
lines changed

docs/quick-starts/framework/next-app-router/README.mdx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,14 @@ export default async function Home() {
6969
}
7070
```
7171

72-
:::tip
72+
:::tip[RSC token caching limitation]
73+
74+
React Server Components cannot write cookies (Next.js limitation). While `getAccessTokenRSC` will still refresh expired tokens using the refresh token, the new access token won't be persisted to the session cookie. This means every RSC request may trigger a token refresh if the cached token has expired.
7375

74-
HTTP does not allow setting cookies after streaming starts, `getAccessTokenRSC` cannot update the cookie value, so if the access token is refreshed, it won't be persisted in the session. It is recommended to use `getAccessToken` function in client side or route handlers.
76+
**Solutions:**
77+
78+
1. **Use client components with Server Actions** - Call `getAccessToken` from a client component via Server Actions, which can update cookies.
79+
2. **Use external session storage** - Configure a `sessionWrapper` with Redis/KV storage. The cookie stores only a fixed session ID while token data lives in external storage, allowing RSC to persist refreshed tokens. See [Use external session storage](#use-external-session-storage) below.
7580

7681
:::
7782

@@ -96,9 +101,9 @@ export default async function Home() {
96101
}
97102
```
98103

99-
:::tip
104+
:::tip[RSC token caching limitation]
100105

101-
HTTP does not allow setting cookies after streaming starts, `getOrganizationTokenRSC` cannot update the cookie value, so if the access token is refreshed, it won't be persisted in the session. It is recommended to use `getOrganizationToken` function in client side or route handlers.
106+
Same as `getAccessTokenRSC`, the refreshed organization token won't be persisted in RSC. See [solutions above](#fetch-access-token-for-the-api-resource).
102107

103108
:::
104109

docs/quick-starts/framework/next/_external-storage.mdx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ import { type SessionWrapper, type SessionData } from '@logto/next';
1818

1919
export class MemorySessionWrapper implements SessionWrapper {
2020
private readonly storage = new Map<string, unknown>();
21+
private currentSessionId?: string;
2122

2223
async wrap(data: unknown, _key: string): Promise<string> {
23-
const sessionId = randomUUID();
24+
// Reuse existing session ID if available, only generate new one for first-time users.
25+
// This is important for environments where cookies cannot be updated (e.g. React Server Components),
26+
// as the session ID in the cookie must remain stable while the data in external storage can be updated.
27+
const sessionId = this.currentSessionId ?? randomUUID();
28+
this.currentSessionId = sessionId;
2429
this.storage.set(sessionId, data);
2530
return sessionId;
2631
}
@@ -30,6 +35,8 @@ export class MemorySessionWrapper implements SessionWrapper {
3035
return {};
3136
}
3237

38+
// Store the session ID for potential reuse in wrap()
39+
this.currentSessionId = value;
3340
const data = this.storage.get(value);
3441
return data ?? {};
3542
}

0 commit comments

Comments
 (0)