Add optional env-activated Kratos OIDC/SSO providers (todo §3); off by default, committed claims mapper, SAML via OIDC bridge note
This commit is contained in:
13
README.md
13
README.md
@@ -139,6 +139,17 @@ auto-merged by `docker compose up`) turns them back off for live editing.
|
||||
| `JWKS_URL` | Kratos tokenizer JWKS | verifies the session JWT (§4) |
|
||||
| `COOKIE_SECRET` / `CSRF_SECRET` | dev throwaways | enforced by `REQUIRE_SECURE_SECRETS` |
|
||||
|
||||
### Social sign-in (SSO)
|
||||
|
||||
Off by default — a clean clone is password-only. Kratos activates a provider purely
|
||||
from the environment (no code, no rebuild): set `SELFSERVICE_METHODS_OIDC_ENABLED=true`
|
||||
and `SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS` to a JSON array of providers (`google`,
|
||||
`microsoft`, …), each carrying its `client_id`/`client_secret` and referencing the
|
||||
committed claims mapper `ory/kratos/oidc/claims.jsonnet`. No creds ⇒ no provider ⇒ no
|
||||
SSO button (§4 derives the buttons from this list). Open-source Kratos has **no native
|
||||
SAML** — front it with an OIDC bridge (Ory Polis) and register that bridge as a generic
|
||||
OIDC provider the same way.
|
||||
|
||||
## Type check & tests
|
||||
|
||||
```bash
|
||||
@@ -444,7 +455,7 @@ src/menu-config.ts loadMenuConfig()/defineMenu(): read config/menu.ts (central
|
||||
views/ Core EJS templates (index = the app-shell People dashboard, 403/404/500, partials/ incl. app shell, nav tree, filter bar, data table, pagination, form field, auth card, menu/popover, theme switch, icon sprite)
|
||||
public/ Static assets under /public/ (css/styles.css + auth.css, favicon, robots.txt)
|
||||
config/menu.ts Central menu override + branding (optional; defaults apply if absent)
|
||||
ory/ Ory service config (kratos/: identity schema + kratos.yml) + storage init (postgres/init/init.sql: one DB per service)
|
||||
ory/ Ory service config (kratos/: identity schema, kratos.yml, oidc/ SSO claims mapper) + storage init (postgres/init/init.sql: one DB per service)
|
||||
plugins/ Drop-in plugin folders (scanned at /app/plugins; bind-mount or bake in) (planned)
|
||||
docs/ Reference docs (plugin-contract.md — the authoritative plugin API)
|
||||
e2e/ Playwright visual + functional E2E (Dockerfile.e2e + compose.e2e.yml run it)
|
||||
|
||||
@@ -20,6 +20,19 @@ selfservice:
|
||||
enabled: true
|
||||
code: # email one-time code — powers recovery + verification (not login)
|
||||
enabled: true
|
||||
# Social sign-in (Google, Microsoft, or SAML via an OIDC bridge like Ory Polis —
|
||||
# OSS Kratos has no native SAML). OFF by default → a clean clone is password-only.
|
||||
# Activate WITHOUT code changes by supplying env (the whole-array form is the only
|
||||
# env-settable one Kratos offers); providers reference the committed claims mapper,
|
||||
# and §4 derives the buttons from this list:
|
||||
# SELFSERVICE_METHODS_OIDC_ENABLED=true
|
||||
# SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS=[{"id":"google","provider":"google",
|
||||
# "client_id":"…","client_secret":"…","scope":["openid","email","profile"],
|
||||
# "mapper_url":"file:///etc/config/kratos/oidc/claims.jsonnet"}]
|
||||
oidc:
|
||||
enabled: false
|
||||
config:
|
||||
providers: []
|
||||
flows:
|
||||
error:
|
||||
ui_url: http://127.0.0.1:3000/error
|
||||
|
||||
16
ory/kratos/oidc/claims.jsonnet
Normal file
16
ory/kratos/oidc/claims.jsonnet
Normal file
@@ -0,0 +1,16 @@
|
||||
// OIDC claims → identity traits mapper (Kratos exposes the provider's claims as
|
||||
// `claims`). Shared by every social provider (Google, Microsoft, OIDC/SAML bridges):
|
||||
// they all expose email + given_name/family_name. Email is required by the schema.
|
||||
local claims = std.extVar('claims');
|
||||
|
||||
{
|
||||
identity: {
|
||||
traits: {
|
||||
email: claims.email,
|
||||
name: {
|
||||
first: if std.objectHas(claims, 'given_name') then claims.given_name else '',
|
||||
last: if std.objectHas(claims, 'family_name') then claims.family_name else '',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -62,6 +62,20 @@ test("recovery + verification run on email code, delivered by a courier", () =>
|
||||
assert.match(compose, /--watch-courier/, "kratos dispatches queued mail (else codes never send)");
|
||||
});
|
||||
|
||||
test("social sign-in is off by default — a clean clone stays password-only", () => {
|
||||
// The oidc method ships present-but-disabled with no providers; operators activate it
|
||||
// purely via env (SELFSERVICE_METHODS_OIDC_*) — no code change, no baked-in creds.
|
||||
assert.match(kratosYml, /oidc:\s*\n\s*enabled:\s*false/, "oidc method is disabled by default");
|
||||
assert.match(kratosYml, /providers:\s*\[\]/, "no providers baked in");
|
||||
});
|
||||
|
||||
test("the committed OIDC claims mapper maps email + name", () => {
|
||||
const mapper = read("ory/kratos/oidc/claims.jsonnet");
|
||||
assert.match(mapper, /email:\s*claims\.email/, "provider email → email trait");
|
||||
assert.match(mapper, /given_name/, "given name → name.first");
|
||||
assert.match(mapper, /family_name/, "family name → name.last");
|
||||
});
|
||||
|
||||
test("compose pins the dev mail catcher to an exact version", () => {
|
||||
const tag = read("compose.override.yml").match(/image:\s*axllent\/mailpit:(\S+)/)?.[1];
|
||||
assert.ok(tag, "compose.override.yml pins a mailpit image");
|
||||
|
||||
2
todo.md
2
todo.md
@@ -60,7 +60,7 @@ everything via Docker.
|
||||
- [x] `postgres` service (pinned tag); separate DB/schema per Kratos/Keto/Hydra. → `compose.yml` `postgres` service pinned to `postgres:18.4-alpine3.23` (verified latest stable PG + newest Alpine the official image ships); `ory/postgres/init/init.sql` (mounted at `docker-entrypoint-initdb.d`) creates one DB per service (`kratos`/`keto`/`hydra`) so each owns its schema + migrations. Dev defaults (`ory`/`ory`, env-overridable for prod), named `pgdata` volume mounted at `/var/lib/postgresql` (PG18+ version-subdir layout — not `/data`), `pg_isready` healthcheck. Web app never connects. Verified live: boots healthy, three DBs present, then torn down. `postgres.test.ts` guards the pin + DB-per-service. typecheck + 112 units green.
|
||||
- [x] `kratos` service (pinned) + `migrate`; identity schema (traits: email, name). → `compose.yml` adds `kratos`/`kratos-migrate` pinned to `oryd/kratos:v26.2.0` (verified latest stable); `kratos-migrate` runs `migrate sql -e --yes` against the per-service `kratos` DB after postgres is healthy, `kratos` waits for it (`service_completed_successfully`). `ory/kratos/identity.schema.json` = email (password identifier, verification/recovery via email) + `name {first,last}`, email required. `ory/kratos/kratos.yml` = bootable baseline: password login, self-service UIs pointing at the web routes (themed in §4), serve URLs, dev-throwaway secrets (prod via env, §3), identity schema wired; DSN via env. Themed flows/SSO/session/tokenizer/JWKS are the next §3/§4 items. Tests-first (`kratos.test.ts`: version pin + migrate-before-serve + DSN→kratos DB + schema traits + schema wiring). Boot-verified: migrate exits 0, kratos serves `/health/ready` 200, serves the identity schema, inits a password login flow; torn down. typecheck + 117 units green.
|
||||
- [x] Kratos self-service flows (login, registration, recovery, verification, settings) → return URLs at our themed pages. → `ory/kratos/kratos.yml`: all five flows enabled, each `ui_url` (+ after/return URLs) points at our web routes (`/login`, `/registration`, `/recovery`, `/verification`, `/settings`; §4 renders the fields). Recovery + verification run on the email `code` method (login stays password-only — `code.passwordless_enabled` left default-off); registration after-hooks `session` + `show_verification_ui`; settings gets `privileged_session_max_age` + `required_aal: highest_available`. Added a `courier` (SMTP) sending to a pinned dev mail catcher — **mailpit** (`axllent/mailpit:v1.30.1`) in `compose.override.yml`, web UI on `:8025`; prod overrides `COURIER_SMTP_CONNECTION_URI`. Kratos `serve` now runs `--watch-courier` so queued codes actually dispatch (without it they sit "queued"). Tests-first (`kratos.test.ts`: five flow ui_urls → our pages, recovery/verification use `code` + courier + `--watch-courier`, mailpit pin). Boot-verified end-to-end: all four public browser-flows 303 → `127.0.0.1:3000/<flow>?flow=…`, a registration delivered a real "Use code … to verify your account" email to mailpit (queue → `sent`); torn down. typecheck + 120 units green.
|
||||
- [ ] Kratos OIDC/SSO providers (Google/Microsoft/SAML) config (secrets via env). **None enabled by default** — a clean clone runs password-only; a provider activates purely by supplying its env creds.
|
||||
- [x] Kratos OIDC/SSO providers (Google/Microsoft/SAML) config (secrets via env). **None enabled by default** — a clean clone runs password-only; a provider activates purely by supplying its env creds. → `ory/kratos/kratos.yml` adds the `oidc` method present-but-disabled with an empty `providers: []` (clean clone = password-only, boots clean). Activation is pure env, no code/rebuild: `SELFSERVICE_METHODS_OIDC_ENABLED=true` + `SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS=[…]` (the whole-array override is the only env-settable form Kratos offers — nested-field env vars aren't supported). Providers (`google`/`microsoft`/OIDC bridges) carry their `client_id`/`client_secret` and reference the committed shared claims mapper `ory/kratos/oidc/claims.jsonnet` (provider claims → `email` + `name{first,last}`). **SAML isn't in OSS Kratos** (Enterprise/Network/Polis only) — documented: front it with an OIDC bridge (Ory Polis) and register that bridge as a generic OIDC provider. README **Social sign-in (SSO)** section documents activation; §4 will derive the buttons from the live provider list. Tests-first (`kratos.test.ts`: oidc disabled + empty by default, mapper maps email/name). Boot-verified both halves: clean stack → login flow has only `default`+`password` groups; a one-off kratos with the SSO env → login flow gains an `oidc` group + a `google` button, no boot errors; torn down. typecheck + 122 units green.
|
||||
- [ ] Kratos session settings (cookie name, lifespan, sliding refresh).
|
||||
- [ ] Kratos tokenizer template `plainpages`: claims `{ sub, email, roles }`, `ttl ≈ 10m`, `jwks_url` signer, `claims_mapper_url` (Jsonnet reading `metadata_admin.roles`).
|
||||
- [ ] Generate + mount the JWT signing JWKS; document key rotation.
|
||||
|
||||
Reference in New Issue
Block a user