@bffless/skills 1.8.1 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@
5
5
  },
6
6
  "metadata": {
7
7
  "description": "BFFless platform skills",
8
- "version": "1.8.1"
8
+ "version": "1.8.2"
9
9
  },
10
10
  "plugins": [
11
11
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bffless/skills",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "description": "BFFless platform skills — usable with Claude Code or any agent via the `skills` CLI",
5
5
  "keywords": [
6
6
  "skills",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bffless",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "description": "BFFless platform skills for Claude Code — deployments, pipelines, proxy rules, chat, traffic splitting, and more",
5
5
  "author": {
6
6
  "name": "BFFless"
@@ -5,46 +5,57 @@ description: Cross-domain authentication using the admin login relay pattern, bu
5
5
 
6
6
  # Authentication
7
7
 
8
- BFFless uses a cross-domain authentication relay pattern. Users authenticate at the workspace's admin domain (`admin.<workspace>`) and are relayed back to the content domain with auth cookies. Auth endpoints on the content domain are accessed via the **built-in `/_bffless/auth/*` endpoints** — no proxy rules required.
8
+ BFFless authenticates users on a single admin host (`admin.<primary-domain>`) and reuses that session across every site it serves. For the common case — a primary domain plus its subdomains — the SuperTokens session cookie (`sAccessToken`) is shared on `.<primary-domain>` and works directly. For the edge case of additional cross-origin custom domains, BFFless relays the session into a per-domain `bffless_access` JWT via a one-time bounce through the admin. Both cases expose the same content-side surface at `/_bffless/auth/*` — built into BFFless nginx, no proxy rules required.
9
9
 
10
10
  ## How Authentication Works
11
11
 
12
- ### Workspace Subdomains
12
+ ### The Common Case: Primary Domain (+ Subdomains)
13
13
 
14
- For workspace subdomains (e.g., `myalias.sandbox.workspace.bffless.app`), SuperTokens session cookies (`sAccessToken`) work directly because they share the parent domain.
14
+ A self-hosted BFFless install has **one primary domain** (e.g., `foo.com` — the root you pick during setup). The admin lives at `admin.foo.com` and content can be served from `foo.com` itself or any subdomain (`bar.foo.com`, `app.foo.com`, etc.). Almost every install runs entirely in this mode.
15
15
 
16
- When a user visits a private deployment and isn't authenticated:
16
+ All of these share `.foo.com` as a parent, so the SuperTokens session cookie (`sAccessToken`) reaches every one of them automatically. There is no `bffless_access` cookie in this mode — the session is always validated against `sAccessToken`. (BFFless multi-tenant hosting at `*.workspace.bffless.app` is mechanically the same setup, with `workspace.bffless.app` playing the role of the primary domain.)
17
17
 
18
- 1. Backend redirects to `https://admin.<workspace>/login?redirect=<original-path>&tryRefresh=true`
19
- 2. The login page attempts a session refresh first (the `tryRefresh` param)
18
+ When a user hits a private deployment on the primary domain and isn't authenticated:
19
+
20
+ 1. Backend redirects to `https://admin.foo.com/login?redirect=<original-path>&tryRefresh=true`
21
+ 2. The login page tries a session refresh first (the `tryRefresh` param), in case the cookie is just expired
20
22
  3. If refresh fails, the user logs in normally
21
23
  4. After login, the user is redirected back to the original path
22
- 5. The `sAccessToken` cookie is valid across all subdomains of the workspace
24
+ 5. The `sAccessToken` cookie now travels with every request to either the content or admin host
25
+
26
+ ### Edge Case: Additional Cross-Origin Custom Domains (`customDomainRelay`)
27
+
28
+ A single BFFless install can also serve content from **additional registered domains** that aren't under the primary — e.g., attaching `bat.com` to a `foo.com` install. This is uncommon for OSS users; reach for it only when you genuinely need to serve content from a separately-owned root domain.
23
29
 
24
- ### Custom Domains (customDomainRelay)
30
+ Because `bat.com` and `foo.com` are different origins, the SuperTokens cookie can't reach `bat.com`. SuperTokens itself has no multi-domain cookie support, so BFFless mints its own short-lived JWT and sets it as `bffless_access` on `bat.com` via a one-time relay through `admin.foo.com`. The `bffless_access` cookie **only exists on these cross-origin custom domains** — it is never set on the primary or any of its subdomains.
25
31
 
26
- For custom domains (e.g., `www.bffless.com`), SuperTokens cookies don't work because they're on a completely different domain. BFFless uses a **domain relay** flow:
32
+ The relay flow:
27
33
 
28
- 1. User visits a private page on `www.bffless.com/portal/`
29
- 2. Frontend detects the user is not authenticated (via `/_bffless/auth/session`)
34
+ 1. User visits a private page on `bat.com/portal/`
35
+ 2. Frontend detects no auth (via `/_bffless/auth/session`)
30
36
  3. Frontend redirects to the admin login with relay params:
31
37
  ```
32
- https://admin.console.bffless.app/login?customDomainRelay=true&targetDomain=www.bffless.com&redirect=%2Fportal%2F
38
+ https://admin.foo.com/login?customDomainRelay=true&targetDomain=bat.com&redirect=%2Fportal%2F
33
39
  ```
34
40
  4. User logs in on the admin domain (or is already logged in via SuperTokens session)
35
41
  5. After login, the frontend calls `POST /api/auth/domain-token` with:
36
42
  ```json
37
- { "targetDomain": "www.bffless.com", "redirectPath": "/portal/" }
43
+ { "targetDomain": "bat.com", "redirectPath": "/portal/" }
38
44
  ```
39
- 6. Backend validates that `targetDomain` is a registered domain for this workspace, then creates a short-lived JWT (the "domain token")
40
- 7. Backend returns a `redirectUrl` pointing to the callback on the custom domain: `https://www.bffless.com/_bffless/auth/callback?token=...&redirect=/portal/`
45
+ 6. Backend validates that `targetDomain` is a registered domain for this workspace, then mints a short-lived JWT (the "domain token")
46
+ 7. Backend returns a `redirectUrl` pointing to the callback on the content domain: `https://bat.com/_bffless/auth/callback?token=...&redirect=/portal/`
41
47
  8. The callback endpoint validates the token, sets `bffless_access` and `bffless_refresh` HttpOnly cookies, and redirects to the original path
42
48
 
43
- ### Important: Use `/_bffless/auth/*`, NOT `/api/auth/*`
49
+ ### Default: Use `/_bffless/auth/*`
50
+
51
+ The `/_bffless/auth/*` endpoints are **built into BFFless nginx** and handled by a dedicated controller — they work on every domain without any configuration. They are separate from the SuperTokens `/api/auth/*` endpoints (which only exist on the admin host).
52
+
53
+ Reach for `/_bffless/auth/*` first. The two situations where it isn't enough are:
44
54
 
45
- The `/_bffless/auth/*` endpoints are **built into BFFless nginx** and handled by a dedicated controller. They are separate from the SuperTokens `/api/auth/*` endpoints. Do NOT use `/api/auth/*` on custom domains those are SuperTokens endpoints that use different cookies (`sAccessToken`) which are not set by the domain relay flow.
55
+ - You need to **clear the SuperTokens session** (true logout) on the primary domain or one of its subdomains.
56
+ - You need an endpoint not in the built-in surface (OAuth start/callback, `session/refresh`, etc.).
46
57
 
47
- The domain relay callback sets `bffless_access` and `bffless_refresh` cookies, which are only recognized by the `/_bffless/auth/*` endpoints. Using `/api/auth/session` instead of `/_bffless/auth/session` will cause a redirect loop because the SuperTokens session check won't find the `bffless_access` cookie.
58
+ For those, set up the [reverse-proxy rule](#advanced-reverse-proxy-to-supertokens-endpoints) and call the proxied `/auth/*` path. The proxy only works on the primary domain (the SuperTokens cookie has to reach the request). On an additional cross-origin custom domain like `bat.com`, stick with `/_bffless/auth/*`.
48
59
 
49
60
  ## Auth Endpoints (Built-in)
50
61
 
@@ -55,7 +66,15 @@ All auth endpoints are available at `/_bffless/auth/*` on any domain served by B
55
66
  | `/_bffless/auth/session` | GET | Check current session — see response shape below |
56
67
  | `/_bffless/auth/refresh` | POST | Refresh an expired access token using the refresh cookie |
57
68
  | `/_bffless/auth/callback` | GET | Exchange a domain relay token for auth cookies |
58
- | `/_bffless/auth/logout` | POST | Clear auth cookies |
69
+ | `/_bffless/auth/logout` | POST | Clear `bffless_access` / `bffless_refresh` cookies (does NOT clear SuperTokens session — see [Advanced](#advanced-reverse-proxy-to-supertokens-endpoints)) |
70
+ | `/_bffless/auth/signin` | POST | In-page email+password sign-in (mints `bffless_access`) |
71
+ | `/_bffless/auth/signup` | POST | In-page email+password sign-up |
72
+ | `/_bffless/auth/forgot-password` | POST | Trigger password-reset email |
73
+ | `/_bffless/auth/reset-password` | POST | Complete password reset with token |
74
+ | `/_bffless/auth/verify-email` | POST | Verify email with token |
75
+ | `/_bffless/auth/send-verification-email` | POST | Resend the verification email |
76
+ | `/_bffless/auth/login-methods` | GET | Enabled auth providers / signup gates |
77
+ | `/_bffless/auth/check-email` | POST | Test if an email exists in the workspace |
59
78
 
60
79
  ### Session Endpoint Response Shape
61
80
 
@@ -73,11 +92,84 @@ All auth endpoints are available at `/_bffless/auth/*` on any domain served by B
73
92
 
74
93
  The `/_bffless/auth/session` endpoint checks auth in this order:
75
94
 
76
- 1. **`bffless_access` cookie** — custom domain JWT issued by the callback flow
77
- 2. **`sAccessToken` cookie** — SuperTokens session (fallback for workspace subdomains)
95
+ 1. **`bffless_access` cookie** — domain-relay JWT issued by the callback flow (cross-origin custom domains)
96
+ 2. **`sAccessToken` cookie** — SuperTokens session (fallback for shared-parent topologies: primary domain & enterprise workspace subdomains)
78
97
 
79
98
  If the access token is expired, it returns `401` with `"try refresh token"` to signal the client should call `/_bffless/auth/refresh`.
80
99
 
100
+ ## Advanced: Reverse-Proxy to SuperTokens Endpoints
101
+
102
+ The built-in `/_bffless/auth/*` controller covers the read path (`session`) and the in-page sign-in / sign-up / password-reset flows, but it is **not a complete proxy to the underlying SuperTokens routes**. Two things specifically are missing:
103
+
104
+ 1. **It can't clear the SuperTokens session.** `/_bffless/auth/logout` only deletes the `bffless_access` / `bffless_refresh` cookies that were minted by the domain-relay callback. The real `sAccessToken` cookie lives on the parent admin domain (`.bffless.app`, `.yourdomain.com`) and is managed by SuperTokens' `signOut()`. There's no built-in endpoint on the content domain that can revoke it.
105
+ 2. **It exposes a curated subset of endpoints.** OAuth callbacks, provider lists (`/api/auth/oauth/*`), `session/refresh` (the SuperTokens-format refresh, distinct from `/_bffless/auth/refresh`), and a few other internal routes are only available under the admin's `/api/auth/*` namespace.
106
+
107
+ When you need any of these from a content domain, set up a **reverse proxy rule** from a prefix path on the content domain to the admin backend's `/api/auth` namespace. This is the same pattern the admin UI itself uses.
108
+
109
+ ### The Proxy Rule (canonical example)
110
+
111
+ For a workspace whose admin lives at `admin.<workspace>`, add an **External Proxy** rule to the content alias:
112
+
113
+ | Field | Value |
114
+ | --------------------------- | ---------------------------------------------- |
115
+ | Path Pattern | `/auth/*` (or `/api/auth/*` — choose one) |
116
+ | Method | Any |
117
+ | Rule Type | External Proxy |
118
+ | Target URL | `http://localhost:3000/api/auth` (same-instance backend) **or** `https://admin.<workspace>/api/auth` (cross-instance) |
119
+ | Strip matched path prefix | ON |
120
+ | Preserve original Host | OFF |
121
+ | Forward cookies to target | **ON** (required — the session cookie has to travel with the request) |
122
+
123
+ `localhost:3000` is the internal CE backend on the same node; BFFless allows HTTP targets only for `*.svc` / `localhost`. For cross-instance setups, point at the admin's HTTPS URL.
124
+
125
+ With the rule above:
126
+
127
+ ```
128
+ GET j5s.dev/auth/session → http://localhost:3000/api/auth/session
129
+ POST j5s.dev/auth/signout → http://localhost:3000/api/auth/signout
130
+ GET j5s.dev/auth/oauth/... → http://localhost:3000/api/auth/oauth/...
131
+ ```
132
+
133
+ ### Response Shape Differs From `_bffless/auth/session`
134
+
135
+ The proxied SuperTokens session endpoint returns a **richer object** than the BFFless one — both `emailVerified` and the session handle, with `user: null` instead of `authenticated: false` for guests:
136
+
137
+ ```json
138
+ // GET /auth/session (proxied to /api/auth/session)
139
+ {
140
+ "session": { "userId": "...", "handle": "..." },
141
+ "user": { "id": "...", "email": "...", "role": "admin" },
142
+ "emailVerified": true,
143
+ "emailVerificationRequired": false
144
+ }
145
+ ```
146
+
147
+ Compare with the BFFless built-in (covered above):
148
+
149
+ ```json
150
+ // GET /_bffless/auth/session
151
+ { "authenticated": true, "user": { "id": "...", "email": "...", "role": "admin" } }
152
+ ```
153
+
154
+ Pick one shape per client and stick with it; mixing causes the same "treated guest as authed" bug described earlier. If you need `emailVerified` on the content domain, use the proxied endpoint.
155
+
156
+ ### When to Use Each
157
+
158
+ | Need | Use |
159
+ | ---------------------------------------------------- | ---------------------------------------------------------- |
160
+ | Cheap session check, no SuperTokens dep | `/_bffless/auth/session` |
161
+ | In-page sign-in / sign-up / forgot-password dialog | `/_bffless/auth/signin` etc. (works on true custom domains too) |
162
+ | Custom-domain relay callback | `/_bffless/auth/callback` (built-in, can't be proxied) |
163
+ | **Clearing the SuperTokens session** (real logout) | Proxied `/auth/signout` **or** bounce through `admin.<workspace>/logout` (see [Logout](#logout)) |
164
+ | OAuth / SSO flows started from the content domain | Proxied `/auth/oauth/*` |
165
+ | `emailVerified`, session handle, pending invitations | Proxied `/auth/session` |
166
+
167
+ ### Caveat: This Only Works When the Cookie Reaches the Proxy
168
+
169
+ The reverse-proxy approach depends on the browser sending the SuperTokens session cookie to the content domain so BFFless can forward it. That works on the **primary domain and its subdomains** (`foo.com`, `bar.foo.com`, …) because `sAccessToken` is on `.foo.com`. It also works on `*.workspace.bffless.app` since that is mechanically the same setup.
170
+
171
+ It does **not** work on additional cross-origin custom domains (a `bat.com` attached to a `foo.com` install): the `sAccessToken` cookie never reaches `bat.com`, so the proxy has nothing to forward. Use `_bffless/auth/*` + the admin-bounce logout on those.
172
+
81
173
  ## Frontend Integration
82
174
 
83
175
  ### Checking Session (with automatic token refresh)
@@ -124,7 +216,7 @@ The flow is: session check → if 401, refresh and retry → inspect `body.authe
124
216
 
125
217
  ### Redirecting to Login
126
218
 
127
- When unauthenticated, redirect the user to the admin login with relay params. Use the **promoted admin domain** (e.g., `admin.console.bffless.app`), not the full workspace subdomain:
219
+ When unauthenticated, redirect the user to the admin on the **primary domain** (e.g., `admin.foo.com`):
128
220
 
129
221
  ```typescript
130
222
  function getLoginUrl(adminLoginUrl: string, redirectPath: string): string {
@@ -141,11 +233,11 @@ function getLoginUrl(adminLoginUrl: string, redirectPath: string): string {
141
233
  return `${adminLoginUrl}?${params.toString()}`;
142
234
  }
143
235
 
144
- // Example: redirect to admin login, then relay back to /portal/
236
+ // Example: redirect to admin login on the primary domain, then relay back to /portal/
145
237
  const session = await checkSession();
146
238
  if (!session) {
147
239
  window.location.href = getLoginUrl(
148
- 'https://admin.console.bffless.app/login',
240
+ 'https://admin.foo.com/login',
149
241
  '/portal/',
150
242
  );
151
243
  }
@@ -153,13 +245,15 @@ if (!session) {
153
245
 
154
246
  ### Logout
155
247
 
156
- Logout is symmetric to login: the admin domain owns the SuperTokens session, so you have to bounce through it to actually revoke. Calling `/_bffless/auth/logout` on its own is **not enough** on workspace subdomains see the dedicated troubleshooting entry below.
248
+ Logout is symmetric to login: the admin host owns the SuperTokens session, so on the primary domain (and its subdomains) you have to bounce through `admin.foo.com/logout` to actually revoke. Calling `/_bffless/auth/logout` on its own is **not enough** — it only clears `bffless_access`, which isn't even set on the primary domain. See the dedicated troubleshooting entry below.
249
+
250
+ > **Alternative for the common case:** if you've configured the [reverse-proxy rule](#advanced-reverse-proxy-to-supertokens-endpoints) (e.g., `/auth/*` → admin `/api/auth`), you can `POST /auth/signout` directly from the content domain instead of bouncing through the admin page. The proxy forwards the SuperTokens session cookie and SuperTokens clears it on `.foo.com`. The admin-bounce below is the universal fallback (and the only option on additional cross-origin custom domains, where the proxy can't reach the cookie).
157
251
 
158
252
  ```typescript
159
253
  async function logout(adminLogoutUrl: string) {
160
254
  // 1. Clear the bffless_access / bffless_refresh cookies that live on this
161
- // domain. No-op on workspace subdomains (those cookies are never set
162
- // there), but required for custom domains.
255
+ // domain. No-op on the primary domain (those cookies are never set
256
+ // there), but required for additional cross-origin custom domains.
163
257
  try {
164
258
  await fetch('/_bffless/auth/logout', {
165
259
  method: 'POST',
@@ -178,7 +272,7 @@ async function logout(adminLogoutUrl: string) {
178
272
  }
179
273
 
180
274
  // Example:
181
- // logout('https://admin.console.bffless.app/logout');
275
+ // logout('https://admin.foo.com/logout');
182
276
  ```
183
277
 
184
278
  Mirrors the login flow — same admin URL pattern, just `/logout` instead of `/login`. If you derive both URLs from a single env var, do it explicitly rather than munging the login URL with regex, so the intent is obvious to the next reader.
@@ -212,8 +306,8 @@ Best when you want to exercise the real cookie/relay flow. Configure your dev se
212
306
  export default defineConfig({
213
307
  server: {
214
308
  proxy: {
215
- '/api': { target: 'https://yourworkspace.bffless.app', changeOrigin: true },
216
- '/_bffless': { target: 'https://yourworkspace.bffless.app', changeOrigin: true },
309
+ '/api': { target: 'https://foo.com', changeOrigin: true },
310
+ '/_bffless': { target: 'https://foo.com', changeOrigin: true },
217
311
  },
218
312
  },
219
313
  });
@@ -283,12 +377,12 @@ Caveats:
283
377
  ## Auth Flow Diagram
284
378
 
285
379
  ```
286
- Custom Domain Flow:
380
+ Additional Custom Domain Flow (edge case):
287
381
  ┌──────────────────┐ JS redirect ┌──────────────────────────┐
288
- www.bffless.com │ ──────────────────→ │ admin.<workspace>/login
382
+ bat.com │ ──────────────────→ │ admin.foo.com/login
289
383
  │ (private page) │ customDomainRelay= │ ?customDomainRelay=true │
290
- │ │ true&targetDomain= │ &targetDomain=www...
291
- └──────────────────┘ www.bffless.com └────────────┬─────────────┘
384
+ │ │ true&targetDomain= │ &targetDomain=bat.com
385
+ └──────────────────┘ bat.com └────────────┬─────────────┘
292
386
  ▲ │
293
387
  │ User logs in (SuperTokens)
294
388
  │ │
@@ -298,7 +392,7 @@ Custom Domain Flow:
298
392
  │ │
299
393
  │ 302 redirect │
300
394
  │ ←─────────────────────────────────────────────┘
301
- │ to: www.bffless.com/_bffless/auth/callback?token=...
395
+ │ to: bat.com/_bffless/auth/callback?token=...
302
396
 
303
397
 
304
398
  ┌──────────────────┐
@@ -310,9 +404,9 @@ Custom Domain Flow:
310
404
 
311
405
  ## Troubleshooting
312
406
 
313
- **User gets stuck in a redirect loop?**
407
+ **User gets stuck in a redirect loop on an additional custom domain (e.g., `bat.com`)?**
314
408
 
315
- - **Most common cause:** Using `/api/auth/session` instead of `/_bffless/auth/session`. The domain relay callback sets `bffless_access` cookies which are only recognized by `/_bffless/auth/*` endpoints. The `/api/auth/*` endpoints check SuperTokens cookies (`sAccessToken`) which are NOT set by the domain relay flow.
409
+ - **Most common cause:** Calling `/api/auth/session` instead of `/_bffless/auth/session`. The relay flow sets `bffless_access`, which only `/_bffless/auth/*` recognizes and `/api/auth/*` doesn't even exist on `bat.com` without a reverse-proxy rule (which wouldn't help anyway, since the `sAccessToken` cookie can't reach `bat.com`).
316
410
  - Verify the custom domain is registered in `domain_mappings` with `isActive = true`
317
411
  - Ensure cookies are being set (requires HTTPS for `Secure` flag)
318
412
 
@@ -323,21 +417,14 @@ Custom Domain Flow:
323
417
 
324
418
  **Session check returns 401 but user just logged in?**
325
419
 
326
- - On custom domains: verify the `/_bffless/auth/callback` was reached and cookies were set
327
- - On workspace subdomains: verify `COOKIE_DOMAIN` is configured for cross-subdomain cookie sharing
420
+ - On the primary domain or one of its subdomains: verify `COOKIE_DOMAIN` is set to `.foo.com` so `sAccessToken` is shared across them
421
+ - On an additional cross-origin custom domain (`bat.com` attached to a `foo.com` install): verify the `/_bffless/auth/callback` was reached and `bffless_access` was set
328
422
  - Check that the `bffless_access` or `sAccessToken` cookie is present in the request
329
423
 
330
- **Admin login URL — use promoted domain, not workspace subdomain:**
331
-
332
- - If the workspace has a promoted domain (e.g., `console.bffless.app`), use `admin.console.bffless.app`, NOT `admin.console.workspace.bffless.app`
333
- - The workspace subdomain format still works but the promoted domain is cleaner
334
-
335
424
  **Logout returns 200 "Logged out successfully" but the next session check still returns `authenticated: true`?**
336
425
 
337
- This is the most-reported logout footgun. It means you called `/_bffless/auth/logout` alone:
426
+ This is the most-reported logout footgun on the primary domain. `/_bffless/auth/logout` only clears `bffless_access` / `bffless_refresh`, but on the primary domain those cookies never existed — the session is in `sAccessToken` on `.foo.com`, which `/_bffless/auth/logout` cannot touch.
338
427
 
339
- - `/_bffless/auth/logout` only clears the **custom-domain JWT cookies** (`bffless_access`, `bffless_refresh`).
340
- - The session endpoint falls back to the **SuperTokens session** (`sAccessToken`) when no `bffless_access` cookie is present. That cookie lives on the parent domain (`.bffless.app` / `.yourdomain.com`) and was set by the admin login — it is not cleared by `/_bffless/auth/logout`.
341
- - On workspace subdomains the `bffless_access` cookie was never set in the first place, so `/_bffless/auth/logout` is effectively a no-op and the SuperTokens fallback re-authenticates the user immediately.
428
+ Fix: either configure the [reverse-proxy rule](#advanced-reverse-proxy-to-supertokens-endpoints) and `POST /auth/signout` directly, or navigate to `admin.foo.com/logout?redirect=<current-page>`. The admin page calls SuperTokens `signOut()`, which revokes the session and clears the shared cookie, then redirects back. See the [Logout](#logout) section for the full pattern.
342
429
 
343
- Fix: after the `/_bffless/auth/logout` call, navigate to `admin.<workspace>/logout?redirect=<current-page>`. The admin page calls SuperTokens `signOut()`, which revokes the session and clears the shared cookie, then redirects back. See the [Logout](#logout) section for the full pattern.
430
+ On a cross-origin custom domain (`bat.com`) `/_bffless/auth/logout` actually does clear the relevant cookies (`bffless_access` / `bffless_refresh`) no admin bounce required there.