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
The hand-written auth guides predated ADR-0069 and were stale:
- auth-sso.mdx: only covered OIDC; added an "Enterprise SSO (SAML 2.0)" section
(Register SAML Provider action, IdP fields, SP ACS/metadata URLs, sign-in flow)
and reframed the ADR-0069 note around the admin-managed Setup → SSO Providers UI.
- authentication.mdx: "Key Features" listed none of the P1/P2 controls; added them
and a canonical "Enterprise Authentication Hardening (ADR-0069)" settings
reference (password policy / anti-abuse / enforced MFA / sessions / network +
how the session-validation gate works). Reframed the stale "do not expose 2FA"
caveat to point at enforced MFA + the Console remediation flow.
- HARDENING.md: expanded the TL;DR auth row, added an "Authentication hardening"
production-settings section, and noted the new session-control settings.
- security.mdx: cross-linked authentication hardening as the first security layer
and added it to the security checklist.
References/* are generated from object/action metadata and self-update; not touched.
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: content/docs/guides/auth-sso.mdx
+60-5Lines changed: 60 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -113,11 +113,14 @@ Enterprise packages can pass `oidcProviders` into `@objectstack/plugin-auth`
113
113
or contribute them through `auth:configure`. The open-source package does not
114
114
ship a generic OIDC settings UI.
115
115
116
-
> **Per-environment external IdP (ADR-0069).** Recent releases add a
117
-
> per-environment external-IdP path built on `@better-auth/sso` (a generic OIDC
118
-
> relying party) and surface an `sso` flag in the public `/auth/config`
119
-
> (`features.sso`) so a client can show an enterprise-login button when SSO is
120
-
> configured. The `oidcProviders` extension below remains the in-process path.
116
+
> **Admin-managed external IdP (ADR-0069).** Recent releases add a
117
+
> per-environment external-IdP path built on `@better-auth/sso` and surface an
118
+
> `sso` flag in the public `/auth/config` (`features.sso`) so a client can show
119
+
> an enterprise-login button when SSO is configured. Admins register providers
120
+
> **without code** from **Setup → SSO Providers** — both **OIDC** (Okta, Entra,
121
+
> Auth0, Keycloak, …) and **SAML 2.0** (see [SAML 2.0](#enterprise-sso-saml-20)
122
+
> below). The `oidcProviders` extension shown here remains the in-process path
123
+
> for framework/enterprise packages that prefer wiring providers in code.
121
124
122
125
### Quick start — Okta
123
126
@@ -204,6 +207,58 @@ const oidcProviders = [
204
207
205
208
---
206
209
210
+
## Enterprise SSO (SAML 2.0)
211
+
212
+
SAML 2.0 is provided natively by `@better-auth/sso` (the same package behind the
213
+
OIDC path) — **no custom plugin or extension is needed**. Admins register a SAML
214
+
IdP from **Setup → SSO Providers → Register SAML Provider**, which posts to the
215
+
env-side bridge at `POST /api/v1/auth/admin/sso/register-saml`.
216
+
217
+
### Register a SAML provider
218
+
219
+
Fill in the IdP details collected by the **Register SAML Provider** action:
220
+
221
+
| Field | Required | Description |
222
+
|---|---|---|
223
+
|`providerId`| ✅ | Stable identifier, e.g. `acme-saml`. |
224
+
|`issuer`| ✅ | The IdP's SAML **EntityID** (issuer), e.g. `https://saml.acme.com/entityid`. |
225
+
|`domain`| ✅ | Users with this email domain are routed to this IdP, e.g. `acme.com`. |
226
+
|`entryPoint`| ✅ | The IdP's SAML single sign-on (redirect) URL that receives the `SAMLRequest`. |
227
+
|`cert`| ✅ | The IdP's X.509 signing certificate (PEM body) — used to verify assertion signatures. |
228
+
|`identifierFormat`| — | Requested SAML NameID format (defaults to the IdP's configured format), e.g. `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`. |
229
+
230
+
On success the response returns the two **Service Provider (SP)** URLs you
-⚙️**2FA backend** - better-auth two-factor plugin wiring is available for custom UIs
36
+
-✅**Multi-Factor Authentication** - TOTP two-factor, plus org-wide **enforced MFA** with a grace period and a Console enrollment/remediation flow (ADR-0069)
37
37
- ✅ **Magic Links** - Passwordless authentication
38
38
- ✅ **Organizations** - Multi-tenant support
39
39
- ✅ **ObjectQL Integration** - Native ObjectStack data persistence (no ORM required)
40
40
41
+
**Enterprise hardening (ADR-0069)** — configured from **Setup → Authentication**, each setting backed by real runtime enforcement:
42
+
43
+
- ✅ **Password policy** - Complexity (min character classes), history (no-reuse), expiry, and breached-password rejection via Have I Been Pwned (k-anonymity)
44
+
- ✅ **Account lockout & rate-limiting** - Per-account lockout after N failed sign-ins (admin **Unlock** action) and per-IP throttling
45
+
- ✅ **Enforced MFA** - Org-wide `require_mfa` with a configurable grace period; the session is gated from data access until the user enrolls
46
+
- ✅ **Session controls** - Idle timeout, absolute lifetime cap, and concurrent-session limit per user
- ✅ **Enterprise SSO** - Admin-managed OIDC and SAML 2.0 trust list (see [Social & Enterprise SSO](/docs/guides/auth-sso))
49
+
50
+
See [Enterprise Authentication Hardening](#enterprise-authentication-hardening-adr-0069) for the full settings reference.
51
+
41
52
### Architecture
42
53
43
54
The plugin uses a **direct forwarding** architecture where all authentication requests are forwarded to Better-Auth's universal handler. This ensures:
@@ -324,13 +335,71 @@ Better-Auth automatically handles the OAuth callback at `/api/v1/auth/callback/g
324
335
325
336
---
326
337
338
+
## Enterprise Authentication Hardening (ADR-0069)
339
+
340
+
Beyond the core flows above, `@objectstack/plugin-auth` ships an enterprise
341
+
hardening layer. Every toggle is configured from **Setup → Authentication** and
342
+
is wired to real runtime enforcement (ADR-0049 — no setting ships without the
343
+
code that reads it). Changes take effect immediately; no restart is required.
344
+
345
+
### Password policy
346
+
347
+
| Setting | Effect |
348
+
|---|---|
349
+
|`password_reject_breached`| Reject passwords found in the Have I Been Pwned corpus (k-anonymity range check — the password is never sent in full). |
350
+
|`password_require_complexity` / `password_min_classes`| Require a minimum number of character classes (upper / lower / digit / symbol). |
351
+
|`password_history_count`| Block reuse of the last *N* passwords. |
352
+
|`password_expiry_days`| Force a password change after *N* days; expired sessions are gated until the password is rotated. |
353
+
354
+
### Anti-abuse (lockout & rate-limiting)
355
+
356
+
| Setting | Effect |
357
+
|---|---|
358
+
|`lockout_threshold`| Lock an account after this many consecutive failed sign-ins. |
359
+
|`lockout_duration_minutes`| How long a lockout lasts. Admins can clear it early with the **Unlock** action on the user record. |
360
+
|`rate_limit_max` / `rate_limit_window_seconds`| Per-IP request throttle on auth endpoints. |
361
+
362
+
### Multi-factor (enforced MFA)
363
+
364
+
| Setting | Effect |
365
+
|---|---|
366
+
|`mfa_required`| Require MFA org-wide (also settable per organization via `require_mfa`). |
367
+
|`mfa_grace_period_days`| Grace window for existing users to enroll. After it elapses, the session is allowed to sign in but **gated from data access** until TOTP is enrolled — the Console surfaces a remediation overlay that walks the user through enrollment. |
368
+
369
+
### Sessions
370
+
371
+
| Setting | Effect |
372
+
|---|---|
373
+
|`session_idle_timeout_minutes`| Expire a session after inactivity. |
374
+
|`session_absolute_max_hours`| Hard cap on total session lifetime regardless of activity. |
375
+
|`max_concurrent_sessions_per_user`| Cap simultaneous sessions; the oldest is revoked (expired in place) when the cap is exceeded. |
376
+
377
+
### Network
378
+
379
+
| Setting | Effect |
380
+
|---|---|
381
+
|`allowed_ip_ranges`| Restrict authenticated access to one or more IPv4 CIDR ranges. IP is extracted with trust-proxy awareness (`x-forwarded-for` / `cf-connecting-ip` / `x-real-ip`); requests whose IP can't be determined fail open. |
382
+
383
+
> **How enforcement works.** Password expiry and enforced MFA share a single
384
+
> *session-validation gate*: `customSession` stamps the session with an
385
+
> `authGate` posture, and the REST server + runtime dispatcher block gated
386
+
> sessions from data access (auth, health, and a small allowlist still pass so
387
+
> the user can remediate). This is why a non-compliant user can sign in but
388
+
> can't read data until they fix the issue.
389
+
390
+
---
391
+
327
392
## Advanced Features
328
393
329
394
### Two-Factor Authentication (2FA)
330
395
331
-
ObjectStack wires the better-auth two-factor plugin at the backend layer, but
332
-
the open-source Console login UI does not yet ship the complete TOTP challenge
333
-
flow. Do not expose 2FA as an end-user feature until your UI handles:
396
+
ObjectStack wires the better-auth two-factor (TOTP) plugin at the backend layer.
397
+
The endpoints below let you build an **opt-in** 2FA experience in a custom account
0 commit comments