Enterprise SSO & directory
In a secure on-prem deployment, users don't type a password into the app — a reverse proxy at the edge terminates Kerberos/SPNEGO or smartcard (mTLS) auth and forwards the established identity as request headers. Two packages turn that into a real session, and both are off by default (a plain checkout keeps email/password login) — the same graceful-degradation contract as every optional integration in the stack.
@tikab-interactive/fusion-edge-ssoanswers "who is it" — a pure, isomorphic resolver that verifies a request came through the trusted edge and reads the identity out of the headers. Header alone is not enough: a shared secret (or IP allow-list) proves the request is really from the edge, so the headers can't be spoofed by anything that reaches the app directly.@tikab-interactive/fusion-ldapanswers "what does the directory know" — it reads a user's AD groups and computes which projects those groups grant, behind aDirectoryClientinterface (a realldaptsadapter, or an in-memory fake for tests). Authentication is never done here; the directory is read with a service-account bind.
Issuing the session
A session + cookie can only be minted cleanly inside a Better Auth endpoint (that's
where the cookie context lives), so the integration is a Better Auth plugin in
fusion-auth: edgeSsoPlugin. It is deliberately generic — the app injects a
resolve(headers) callback, so the plugin depends on no specific header library.
It mounts a /api/auth/edge/sign-in endpoint that resolves the identity, finds (or,
opt-in, provisions) the user, and signs them in — modelled on Better Auth's own
first-party anonymous plugin (internalAdapter.createSession → setSessionCookie).
export const auth = createAuth({ db, plugins: edgeSsoPlugins() });
export const { getSession, requireSession } = createAuthSession(auth, db);edgeSsoPlugins() returns [] unless SSO_ENABLED, so this is a no-op by default.
The example adapts fusion-edge-sso's SsoResult to the plugin's library-agnostic
EdgeResolution in src/lib/edge-sso.ts, and the login page shows a "Sign in with
SSO" affordance only when SSO is enabled (a loader reads the flag server-side).
The endpoint handles all four outcomes explicitly: authenticated → sign in and
redirect; challenge → 401 WWW-Authenticate: Negotiate (domain browsers retry with
a Kerberos ticket); anonymous → redirect to the form-login fallback; error → 403.
Directory sync
On edge sign-in, the plugin's best-effort afterSignIn hook reconciles the user's
AD-group → ProcessN-project memberships (src/lib/ldap-sync.ts): it reads the
directory groups, runs the pure planProjectMemberships diff, and applies the change
set to processn_projectuser (granting/revoking only is_ad_synced rows — manual
grants are never touched). A failure here never blocks an otherwise-valid sign-in.
Configuration
Everything is environment-driven; unset means off.
| Variable | Purpose |
|---|---|
SSO_ENABLED | Master switch for edge SSO. |
SSO_TRUST_MODE | secret (default) · ip · secret_and_ip · none. |
SSO_EDGE_SECRET | The shared secret the edge injects (anti-spoofing). |
SSO_EMAIL_DOMAIN | Derives username@domain to match/provision the app user. |
SSO_PROVISION_USERS | Create users the directory knows but the app hasn't seen (default off). |
LDAP_ENABLED, LDAP_URL, LDAP_BIND_DN, … | Service-account directory access (see fusion-ldap). |
This is the air-gapped delivery posture: the reverse proxy is the security boundary, the app trusts it via the secret, and no model or cloud call is involved — sign-in works fully offline.