|
| 1 | +# Anonymous Sessions Example |
| 2 | + |
| 3 | +This example demonstrates anonymous sessions (EA feature) in @auth0/nextjs-auth0. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +- **Create Anonymous Sessions**: Generate anonymous identity before login with optional metadata |
| 8 | +- **SSR Seed**: Server-side anonymous session fetch prevents loading flash |
| 9 | +- **Login-to-Link**: Convert anonymous session to authenticated account |
| 10 | +- **Protected API**: Use anonymous access token to call audience-protected resources |
| 11 | +- **Client Hook**: `useAnonymousSession()` for live session state |
| 12 | +- **Security Best Practices**: SEC-1 (no session_token in authorizationParameters), metadata set-once |
| 13 | + |
| 14 | +## Prerequisites |
| 15 | + |
| 16 | +1. **Build SDK First**: Run `pnpm build` at the repository root to compile the SDK (required for `file:../..` dependency) |
| 17 | +2. **Tenant Configuration**: See `../../.forge/features/anonymous-sessions/poc/lite/TENANT-SETUP.md` for required Auth0 tenant setup: |
| 18 | + - `oidc_conformant: true` on client |
| 19 | + - `anonymous_sessions.active: true` on client |
| 20 | + - Client grant with `subject_type: anonymous_user` for audience `https://api.customers` |
| 21 | +3. **Environment Variables**: Copy `.env.example` to `.env.local` and fill in your Auth0 tenant credentials |
| 22 | + |
| 23 | +## Setup |
| 24 | + |
| 25 | +```bash |
| 26 | +# 1. Install dependencies (from example directory) |
| 27 | +pnpm install |
| 28 | + |
| 29 | +# 2. Configure .env.local |
| 30 | +cp .env.example .env.local |
| 31 | +# Edit .env.local with your Auth0 tenant credentials |
| 32 | + |
| 33 | +# 3. Run development server |
| 34 | +pnpm dev |
| 35 | +``` |
| 36 | + |
| 37 | +Visit http://localhost:3000 |
| 38 | + |
| 39 | +## Demo Flow |
| 40 | + |
| 41 | +1. **Home Page** (`/`): |
| 42 | + - "Enter as Guest" → creates anonymous session via GET route |
| 43 | + - "Create with Metadata" → creates session with custom metadata (e.g., cart state) |
| 44 | + - Fail-loud diagnostic if tenant misconfigured (403 unauthorized_client) |
| 45 | + |
| 46 | +2. **Demo Page** (`/demo`): |
| 47 | + - SSR session display (may be stale, D7) |
| 48 | + - Client-side live session panel (`useAnonymousSession()`) |
| 49 | + - Logout/Renew/Invalidate buttons |
| 50 | + - "Login to Link" → converts anonymous session to authenticated account (SEC-1: SDK injects session_token from cookie, no param) |
| 51 | + - "Fetch Products" → calls protected API with anonymous access token |
| 52 | + |
| 53 | +## API Routes |
| 54 | + |
| 55 | +- `GET /auth/anonymous-session` — SDK route: creates session, redirects to returnTo |
| 56 | +- `POST /auth/anonymous-session/logout` — SDK route: clears cookie (no server-side revocation) |
| 57 | +- `POST /api/anon/create` — App route: creates session with metadata |
| 58 | +- `GET /api/products` — App route: uses access token to call protected API (stub) |
| 59 | + |
| 60 | +## Security Notes |
| 61 | + |
| 62 | +- **SEC-1 (Session Token Fixation)**: SDK injects session_token from its own cookie during login (3-layer protection). Applications must NOT allow session_token in authorizationParameters. |
| 63 | +- **Metadata Set-Once**: Metadata can only be set at creation time, cannot be updated after (CASCADE-v2 M2). |
| 64 | +- **No Server Revocation**: Logout clears the client cookie but does NOT revoke the session server-side; tokens remain valid until expiry. |
| 65 | +- **SSR Staleness (D7)**: Server Component `getAnonymousSession()` reads may be stale; renewal is deferred to route handlers. |
| 66 | + |
| 67 | +## Scripts |
| 68 | + |
| 69 | +- `pnpm dev` — Start development server |
| 70 | +- `pnpm build` — Build for production |
| 71 | +- `pnpm start` — Run production build |
| 72 | +- `pnpm lint` — Run ESLint |
| 73 | +- `pnpm test` — Unit + MSW tests (Vitest) |
| 74 | +- `pnpm test:e2e:l9` — SEC-1 session_token-strip browser test (Playwright, creds-free) |
| 75 | +- `pnpm test:e2e` — All Playwright specs |
| 76 | + |
| 77 | +## Testing |
| 78 | + |
| 79 | +Unit/integration (Vitest): `pnpm test`. Deterministic MSW-backed and live-tenant |
| 80 | +tiers; the live server tier skips without tenant creds. |
| 81 | + |
| 82 | +Browser (Playwright, `tests/e2e/`): |
| 83 | + |
| 84 | +> **Note:** These browser tests use an example-local Playwright harness. When the repo-wide e2e suite (`feat/e2e-test-suite`, root `e2e/`) lands in main, this coverage will fold into that centralized harness and adopt its shared login helper (`loginWithAuth0`) and `injectSession` conventions. |
| 85 | +
|
| 86 | +**Offline Mock Tier** |
| 87 | + |
| 88 | +The offline tier tests the example wiring and SDK logic against a mock Auth0 tenant. The mock (injected via `customFetch` at `lib/mock/anon-mock-fetch.ts`) intercepts `POST /anonymous/token` and `POST /anonymous/logout` requests and returns configurable responses (success, expired, 403/400/500 errors). The SDK's real encrypt/persist/decrypt/renew logic executes normally. No tenant credentials required. |
| 89 | + |
| 90 | +Run: `pnpm test:e2e:offline`. Runs 17 specs covering session lifecycle (create, get, renew, logout), error paths (feature_not_enabled, unauthorized_client, invalid_target, invalid_scope, server_error, metadata_too_large), and UI states (loading, error banner, metadata display). |
| 91 | + |
| 92 | +**Live Tier** |
| 93 | + |
| 94 | +- **L9 — SEC-1 strip** (`sec1-strip.spec.ts`): proves a caller-supplied |
| 95 | + `session_token` on `/auth/login` never reaches `/authorize`. L9a/L9c are |
| 96 | + creds-free (CI-safe); L9b mints a real `auth0_anon` cookie and needs |
| 97 | + `.env.local`. Run: `pnpm test:e2e:l9`. |
| 98 | +- **L10 — callback link** (`link-callback.spec.ts`): drives a real Universal |
| 99 | + Login and asserts the callback links the anonymous session (`?linked=true` |
| 100 | + banner). Gated; skips unless a tenant test user is supplied: |
| 101 | + |
| 102 | + ```bash |
| 103 | + TEST_USER_EMAIL=you@example.com TEST_USER_PASSWORD=... \ |
| 104 | + pnpm exec playwright test link-callback |
| 105 | + ``` |
| 106 | + |
| 107 | + First run installs the browser: `pnpm exec playwright install chromium`. |
| 108 | + |
| 109 | +## Production Checklist |
| 110 | + |
| 111 | +This example includes test-only files for the offline e2e harness. Before deploying to production or using this example as a template for a real application, remove the following directories and files: |
| 112 | + |
| 113 | +- `lib/mock/` — offline mock fetch for E2E_ANON_MOCK mode |
| 114 | +- `app/api/test/` — scenario injection route for test harness |
| 115 | +- `playwright.offline.config.ts` and `tests/e2e/offline/` |
| 116 | +- The `E2E_ANON_MOCK` gated `customFetch` line in `lib/auth0.ts` |
| 117 | + |
| 118 | +These files exist solely to support offline testing without Auth0 tenant credentials. They have no role in a production application and should never be deployed to dev, staging, or production environments. |
| 119 | + |
| 120 | +## Dependencies |
| 121 | + |
| 122 | +- `@auth0/nextjs-auth0`: `file:../..` (local SDK build) |
| 123 | +- `next`: 16.2.5 |
| 124 | +- `react`: 19.2.1 |
| 125 | + |
| 126 | +## References |
| 127 | + |
| 128 | +- [Anonymous Sessions Spec](../../.forge/features/anonymous-sessions/poc/lite/BUILD-SPEC.md) |
| 129 | +- [Tenant Setup](../../.forge/features/anonymous-sessions/poc/lite/TENANT-SETUP.md) |
| 130 | +- [Wire Contract](../../.forge/features/anonymous-sessions/poc/lite/WIRE-CONTRACT-LIVE.md) |
0 commit comments