Skip to content

Commit

Permalink
feat: add next.js external storage (#951)
Browse files Browse the repository at this point in the history
  • Loading branch information
wangsijie authored Jan 8, 2025
1 parent ffcc985 commit c6441e5
Show file tree
Hide file tree
Showing 21 changed files with 380 additions and 35 deletions.
5 changes: 5 additions & 0 deletions docs/quick-starts/framework/next-app-router/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ sidebar_custom_props:

import ApiResourcesDescription from '../../fragments/_api-resources-description.md';
import FurtherReadings from '../../fragments/_further-readings.md';
import ExternalStorage from '../next/_external-storage.mdx';
import Installation from '../next/_installation.mdx';

import GetUserInformation from './_get-user-information.mdx';
Expand Down Expand Up @@ -101,6 +102,10 @@ HTTP does not allow setting cookies after streaming starts, `getOrganizationToke

:::

## Use external session storage \{#use-external-session-storage}

<ExternalStorage />

## Further readings \{#further-readings}

<FurtherReadings />
5 changes: 5 additions & 0 deletions docs/quick-starts/framework/next/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ framework: Next.js
import ApiResourcesDescription from '../../fragments/_api-resources-description.md';
import FurtherReadings from '../../fragments/_further-readings.md';

import ExternalStorage from './_external-storage.mdx';
import GetUserInformation from './_get-user-information.mdx';
import GuideTip from './_guide-tip.mdx';
import Installation from './_installation.mdx';
Expand Down Expand Up @@ -96,6 +97,10 @@ export const config = {
Check the [next-sample](https://github.com/logto-io/js/tree/master/packages/next-sample) to see full example.
:::

## Use external session storage \{#use-external-session-storage}

<ExternalStorage />

## Further readings \{#further-readings}

<FurtherReadings />
39 changes: 39 additions & 0 deletions docs/quick-starts/framework/next/_external-storage.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
The SDK uses cookies to store encrypted session data by default. This approach is secure, requires no additional infrastructure, and works especially well in serverless environments like Vercel.

However, there are times when you might need to store session data externally. For instance, when your session data grows too large for cookies, especially when you need to maintain multiple active organization sessions simultaneously. In these cases, you can implement external session storage using the `sessionWrapper` option:

```ts
import { MemorySessionWrapper } from './storage';

export const config = {
// ...
sessionWrapper: new MemorySessionWrapper(),
};
```

```ts
import { randomUUID } from 'node:crypto';

import { type SessionWrapper, type SessionData } from '@logto/next';

export class MemorySessionWrapper implements SessionWrapper {
private readonly storage = new Map<string, unknown>();

async wrap(data: unknown, _key: string): Promise<string> {
const sessionId = randomUUID();
this.storage.set(sessionId, data);
return sessionId;
}

async unwrap(value: string, _key: string): Promise<SessionData> {
if (!value) {
return {};
}

const data = this.storage.get(value);
return data ?? {};
}
}
```

The above implementation uses a simple in-memory storage. In a production environment, you might want to use a more persistent storage solution, such as Redis or a database.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ sidebar_custom_props:

import ApiResourcesDescription from '../../fragments/_api-resources-description.md';
import FurtherReadings from '../../fragments/_further-readings.md';
import ExternalStorage from '../next/_external-storage.mdx';
import Installation from '../next/_installation.mdx';

import GetUserInformation from './_get-user-information.mdx';
Expand Down Expand Up @@ -101,6 +102,10 @@ HTTP erlaubt es nicht, Cookies zu setzen, nachdem das Streaming begonnen hat. `g

:::

## Externen Sitzungspeicher verwenden \{#use-external-session-storage}

<ExternalStorage />

## Weiterführende Lektüre \{#further-readings}

<FurtherReadings />
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ framework: Next.js
import ApiResourcesDescription from '../../fragments/_api-resources-description.md';
import FurtherReadings from '../../fragments/_further-readings.md';

import ExternalStorage from './_external-storage.mdx';
import GetUserInformation from './_get-user-information.mdx';
import GuideTip from './_guide-tip.mdx';
import Installation from './_installation.mdx';
Expand Down Expand Up @@ -96,6 +97,10 @@ export const config = {
Sieh dir das [next-sample](https://github.com/logto-io/js/tree/master/packages/next-sample) an, um ein vollständiges Beispiel zu sehen.
:::

## Externen Sitzungs-Speicher verwenden \{#use-external-session-storage}

<ExternalStorage />

## Weiterführende Lektüre \{#further-readings}

<FurtherReadings />
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Das SDK verwendet standardmäßig Cookies, um verschlüsselte Sitzungsdaten zu speichern. Dieser Ansatz ist sicher, erfordert keine zusätzliche Infrastruktur und funktioniert besonders gut in serverlosen Umgebungen wie Vercel.

Es gibt jedoch Zeiten, in denen du Sitzungsdaten extern speichern musst. Zum Beispiel, wenn deine Sitzungsdaten zu groß für Cookies werden, insbesondere wenn du mehrere aktive Organisation-Sitzungen gleichzeitig aufrechterhalten musst. In diesen Fällen kannst du eine externe Sitzungspeicherung mit der Option `sessionWrapper` implementieren:

```ts
import { MemorySessionWrapper } from './storage';

export const config = {
// ...
sessionWrapper: new MemorySessionWrapper(),
};
```

```ts
import { randomUUID } from 'node:crypto';

import { type SessionWrapper, type SessionData } from '@logto/next';

export class MemorySessionWrapper implements SessionWrapper {
private readonly storage = new Map<string, unknown>();

async wrap(data: unknown, _key: string): Promise<string> {
const sessionId = randomUUID();
this.storage.set(sessionId, data);
return sessionId;
}

async unwrap(value: string, _key: string): Promise<SessionData> {
if (!value) {
return {};
}

const data = this.storage.get(value);
return data ?? {};
}
}
```

Die obige Implementierung verwendet einen einfachen In-Memory-Speicher. In einer Produktionsumgebung möchtest du möglicherweise eine beständigere Speicherlösung verwenden, wie Redis oder eine Datenbank.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ sidebar_custom_props:

import ApiResourcesDescription from '../../fragments/_api-resources-description.md';
import FurtherReadings from '../../fragments/_further-readings.md';
import ExternalStorage from '../next/_external-storage.mdx';
import Installation from '../next/_installation.mdx';

import GetUserInformation from './_get-user-information.mdx';
Expand Down Expand Up @@ -43,7 +44,7 @@ import FetchOrganizationTokenForUser from './api-resources/_fetch-organization-t

<ApiResourcesDescription />

### Configurar cliente Logto \{#configure-logto-client}
### Configurar el cliente de Logto \{#configure-logto-client}

<ConfigApiResources />

Expand All @@ -70,7 +71,7 @@ export default async function Home() {

:::tip

HTTP no permite establecer cookies después de que comienza la transmisión, `getAccessTokenRSC` no puede actualizar el valor de la cookie, por lo que si el token de acceso se actualiza, no se conservará en la sesión. Se recomienda usar la función `getAccessToken` en el lado del cliente o en los manejadores de rutas.
HTTP no permite establecer cookies después de que comienza la transmisión, `getAccessTokenRSC` no puede actualizar el valor de la cookie, por lo que si el token de acceso se actualiza, no se persistirá en la sesión. Se recomienda usar la función `getAccessToken` en el lado del cliente o en los manejadores de rutas.

:::

Expand All @@ -97,10 +98,14 @@ export default async function Home() {

:::tip

HTTP no permite establecer cookies después de que comienza la transmisión, `getOrganizationTokenRSC` no puede actualizar el valor de la cookie, por lo que si el token de acceso se actualiza, no se conservará en la sesión. Se recomienda usar la función `getOrganizationToken` en el lado del cliente o en los manejadores de rutas.
HTTP no permite establecer cookies después de que comienza la transmisión, `getOrganizationTokenRSC` no puede actualizar el valor de la cookie, por lo que si el token de acceso se actualiza, no se persistirá en la sesión. Se recomienda usar la función `getOrganizationToken` en el lado del cliente o en los manejadores de rutas.

:::

## Usar almacenamiento de sesión externo \{#use-external-session-storage}

<ExternalStorage />

## Lecturas adicionales \{#further-readings}

<FurtherReadings />
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ framework: Next.js
import ApiResourcesDescription from '../../fragments/_api-resources-description.md';
import FurtherReadings from '../../fragments/_further-readings.md';

import ExternalStorage from './_external-storage.mdx';
import GetUserInformation from './_get-user-information.mdx';
import GuideTip from './_guide-tip.mdx';
import Installation from './_installation.mdx';
Expand Down Expand Up @@ -78,7 +79,7 @@ export const logtoClient = new LogtoClient({
});
```

Luego, establece el runtime en `experimental-edge` o `edge` en la ruta de la API.
Luego establece el runtime en `experimental-edge` o `edge` en la ruta de la API.

```ts title="pages/api/logto/sign-in.ts"
import { logtoClient } from '../../../../libraries/logto';
Expand All @@ -96,6 +97,10 @@ export const config = {
Consulta el [next-sample](https://github.com/logto-io/js/tree/master/packages/next-sample) para ver un ejemplo completo.
:::

## Usar almacenamiento de sesión externo \{#use-external-session-storage}

<ExternalStorage />

## Lecturas adicionales \{#further-readings}

<FurtherReadings />
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
El SDK utiliza cookies para almacenar datos de sesión cifrados por defecto. Este enfoque es seguro, no requiere infraestructura adicional y funciona especialmente bien en entornos sin servidor como Vercel.

Sin embargo, hay ocasiones en las que podrías necesitar almacenar los datos de sesión externamente. Por ejemplo, cuando tus datos de sesión crecen demasiado para las cookies, especialmente cuando necesitas mantener múltiples sesiones activas de organización simultáneamente. En estos casos, puedes implementar un almacenamiento de sesión externo utilizando la opción `sessionWrapper`:

```ts
import { MemorySessionWrapper } from './storage';

export const config = {
// ...
sessionWrapper: new MemorySessionWrapper(),
};
```

```ts
import { randomUUID } from 'node:crypto';

import { type SessionWrapper, type SessionData } from '@logto/next';

export class MemorySessionWrapper implements SessionWrapper {
private readonly storage = new Map<string, unknown>();

async wrap(data: unknown, _key: string): Promise<string> {
const sessionId = randomUUID();
this.storage.set(sessionId, data);
return sessionId;
}

async unwrap(value: string, _key: string): Promise<SessionData> {
if (!value) {
return {};
}

const data = this.storage.get(value);
return data ?? {};
}
}
```

La implementación anterior utiliza un almacenamiento en memoria simple. En un entorno de producción, podrías querer usar una solución de almacenamiento más persistente, como Redis o una base de datos.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ slug: /quick-starts/next-app-router
sidebar_label: Next.js (App Router)
sidebar_custom_props:
logoFilename: 'next.svg'
darkLogoFilename: 'next-dark.svg'
description: Next.js App Router est un nouveau paradigme pour construire des applications en utilisant les dernières fonctionnalités de React.
---

import ApiResourcesDescription from '../../fragments/_api-resources-description.md';
import FurtherReadings from '../../fragments/_further-readings.md';
import ExternalStorage from '../next/_external-storage.mdx';
import Installation from '../next/_installation.mdx';

import GetUserInformation from './_get-user-information.mdx';
Expand All @@ -34,7 +36,7 @@ import FetchOrganizationTokenForUser from './api-resources/_fetch-organization-t

<Integration />

## Récupérer les informations de l'utilisateur \{#fetch-user-information}
## Récupérer les informations utilisateur \{#fetch-user-information}

<GetUserInformation />

Expand All @@ -46,7 +48,7 @@ import FetchOrganizationTokenForUser from './api-resources/_fetch-organization-t

<ConfigApiResources />

### Récupérer un jeton d’accès pour la ressource API \{#fetch-access-token-for-the-api-resource}
### Récupérer le jeton d’accès pour la ressource API \{#fetch-access-token-for-the-api-resource}

<FetchAccessTokenForApiResources />

Expand All @@ -61,7 +63,7 @@ export default async function Home() {

return (
<main>
<p>Access token: {accessToken}</p>
<p>Jeton d’accès : {accessToken}</p>
</main>
);
}
Expand All @@ -73,7 +75,7 @@ HTTP ne permet pas de définir des cookies après le début du streaming, `getAc

:::

### Récupérer des jetons d’organisation \{#fetch-organization-tokens}
### Récupérer les jetons d’organisation \{#fetch-organization-tokens}

<FetchOrganizationTokenForUser />

Expand All @@ -88,7 +90,7 @@ export default async function Home() {

return (
<main>
<p>Organization token: {token}</p>
<p>Jeton d’organisation : {token}</p>
</main>
);
}
Expand All @@ -100,6 +102,10 @@ HTTP ne permet pas de définir des cookies après le début du streaming, `getOr

:::

## Utiliser un stockage de session externe \{#use-external-session-storage}

<ExternalStorage />

## Lectures complémentaires \{#further-readings}

<FurtherReadings />
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ framework: Next.js
import ApiResourcesDescription from '../../fragments/_api-resources-description.md';
import FurtherReadings from '../../fragments/_further-readings.md';

import ExternalStorage from './_external-storage.mdx';
import GetUserInformation from './_get-user-information.mdx';
import GuideTip from './_guide-tip.mdx';
import Installation from './_installation.mdx';
Expand Down Expand Up @@ -70,8 +71,8 @@ import LogtoClient from '@logto/next/edge';
export const logtoClient = new LogtoClient({
appId: '<your-application-id>',
appSecret: '<your-app-secret-copied-from-console>',
endpoint: '<your-logto-endpoint>', // Par exemple http://localhost:3001
baseUrl: '<your-nextjs-app-base-url>', // Par exemple http://localhost:3000
endpoint: '<your-logto-endpoint>', // Par exemple, http://localhost:3001
baseUrl: '<your-nextjs-app-base-url>', // Par exemple, http://localhost:3000
cookieSecret: 'complex_password_at_least_32_characters_long',
cookieSecure: process.env.NODE_ENV === 'production',
resources: ['<your-api-resource>'],
Expand All @@ -96,6 +97,10 @@ export const config = {
Consultez le [next-sample](https://github.com/logto-io/js/tree/master/packages/next-sample) pour voir un exemple complet.
:::

## Utiliser un stockage de session externe \{#use-external-session-storage}

<ExternalStorage />

## Lectures complémentaires \{#further-readings}

<FurtherReadings />
Loading

0 comments on commit c6441e5

Please sign in to comment.