You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 07765ba
Browse filesBrowse the repository at this point in the historyBrowse files
fix(auth): admin member management works without the admin's own membership (#881)
adminAddMember forwarded the caller's headers into Better Auth's addMember,
which checks the caller's ORGANIZATION permissions — a platform admin who was
not a member of the target organization (the whole point of an admin route)
got FORBIDDEN. The mutation is now a direct write like the rest of
AdminOrganizationsService: membership-limit policy enforced, duplicate
memberships refused. Tests now cover adding a member to an organization the
admin does not belong to — the case the old fixtures masked by creating every
organization owned by the admin.
- adminUpdateMemberRole refuses to demote an organization's only owner,
matching adminRemoveMember's last-owner guard
- getOrganizationForAdmin uses the standard requireAdmin middleware
- crossSubDomainCookies gains config-time warnings for the two silent
browser failures: a cookie domain that does not cover the baseUrl host,
and localhost/loopback base URLs
- README documents the full multi-app session recipe: shared cookie domain +
shared BETTER_AUTH_SECRET + shared postgres:// AUTH_DATABASE_URL (ADR 0027)
Copy file name to clipboardExpand all lines: plugins/auth/README.md
+9-1Lines changed: 9 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -65,7 +65,15 @@ It runs inside the **everything-plugin** framework (oRPC + Effect) and is design
65
65
66
66
`onboard` hrefs must be app paths (`/…`) or `https://` URLs. `links.next`, `links.home` and `community.url` replace `{organizationId}` with the joined organization's ID. A failed or invalid `community.url` response shows nothing extra.
67
67
68
-
`crossSubDomainCookies` scopes the session cookie to a shared registrable domain so sibling subdomains (e.g. `app.example.com` and `api.example.com`) share one session. Every app on that domain must run the **same `BETTER_AUTH_SECRET`** — the cookie is sealed with it — and each app should list its siblings' origins in `trustedOrigins`. Absent or disabled config keeps cookies host-scoped.
68
+
`crossSubDomainCookies` scopes the session cookie to a shared registrable domain so sibling subdomains (e.g. `app.example.com` and `api.example.com`) share one session. Absent or disabled config keeps cookies host-scoped.
69
+
70
+
**Multi-app session sharing** — a family of apps whose sessions roam a shared registrable domain needs all three legs together; the cookie alone is not enough:
71
+
72
+
1.**Shared cookie** — each app sets `crossSubDomainCookies: { enabled: true, domain: ".example.com" }` where `example.com` covers every app's host. A cookie whose `Domain` does not cover the app's own host is dropped by the browser, which breaks sign-in on that app (the boot warnings flag this, along with localhost hosts, which can never carry a `Domain` attribute).
73
+
2.**Shared secret** — every app on the domain must run the **same `BETTER_AUTH_SECRET`**: the cookie is sealed with it, so a sibling that runs a different secret sees every incoming session as invalid and signs the user out.
74
+
3.**Shared database** — every app's `AUTH_DATABASE_URL` must point at the **same `postgres://` database**. Auth tables live un-namespaced in that database (`namespace: false` — Better Auth's schema owns the public tables), so the shared cookie resolves the same session row everywhere. Without it, the cookie resolves to a session row the sibling cannot see and the request still reads as signed-out. The pglite default is per-identity and dev-only; cross-app session sharing is a `postgres://` setup.
75
+
76
+
Each app should also cross-list its siblings' origins in `trustedOrigins`. The everything.dev family recipe this configuration mirrors is documented in `docs/adr/0027-apps-decomposition-every-app.md`.
"crossSubDomainCookies is enabled but the auth baseUrl is a localhost host — browsers refuse Domain cookies for local hosts, so sessions stay host-scoped in this environment",
97
+
hint: "Exercise cross-domain session sharing on a staging deployment whose hosts share the configured registrable domain instead",
message: `crossSubDomainCookies.domain "${cookies.domain}" does not cover the auth baseUrl host "${host}" — browsers drop cookies scoped to a foreign domain, which breaks sign-in on this app`,
107
+
hint: `Use a dot-prefixed registrable domain that is a suffix of "${host}" (e.g. ".${host.split(".").slice(-2).join(".")}")`,
0 commit comments