---
id: w4/m11/t001
title: Kratos values — enable totp, webauthn (second factor), lookup_secret, AAL policy + prod RP ID/origins
worker: worker4
status: done
estimate: 30m
depends_on: []
---

## Objective

Turn on the second-factor credential methods in Kratos configuration: `totp` (issuer named for bex), `webauthn` **as second factor** (`webauthn.config.passwordless: false` — passkey mode and the separate `passkey` method are first-factor-only and cannot satisfy the aal2 challenge the DoD demands), and `lookup_secret` (recovery codes), with the session/settings AAL policy set so enrolling a second factor makes login require it (`highest_available`) — configuration only, no custom code. The prod WebAuthn RP config lands **in this same base edit** (base values ARE prod, auto-synced by Argo): RP ID `bex.co` (the registrable parent — dashboard on `dashboard.bex.co`, Kratos on `auth.bex.co`), origins `[https://dashboard.bex.co]`; the local overlay overrides `rp.id → localhost`, origins `[http://localhost:5173]`.

## Context

- Kratos values live at `deploy/gitops/base/values/kratos.values.yaml` (m1's ADR: identity is bought — docs/ADR012-auth.md).
- _2026-07-11:_ **base values ARE prod** — there is no prod Kratos overlay (`overlays/prod/values` carries only openbao+traefik); `base/values/kratos.values.yaml` is the live config (`base_url: https://auth.bex.co`) and the kratos Application auto-syncs main with prune+selfHeal. Kratos requires `rp.id`/`rp.origins` when webauthn is enabled, so deferring the RP config to t004 either crashloops prod on the next sync or (with a placeholder) strands early enrollments — the exact RP-ID-is-forever pitfall. Ship the real RP config here.
- Preserve the m19 platform-pool pins while editing (the `deployment:`/`job:` nodeSelector + tolerations blocks in kratos.values.yaml) — do not clobber them.
- **Precondition for step 3 (2026-07-11):** no mock node carries `bex.co/pool=platform` post-m19 and the local overlay doesn't override the selector, so a fresh local sync leaves Kratos (and the whole auth stack) Pending — blocked on the `w1` inbox note on mock pool-labels; alternatively verify the rendered config plus prod (base values are live there anyway). If the local-overlay sweep lands here, budget +15–20m — it unblocks every future local-overlay milestone, not just m11.
- `session.whoami.required_aal` / settings-flow `required_aal` set to `highest_available` so a user with a second factor must present it, while password-only users are untouched.
- Watch for local-overlay quirks (the m7 pattern: control-plane pin + PSA) if any new component is involved — none should be; this is values-only.

## Steps

1. Enable the three methods in `kratos.values.yaml` with sane parameters (TOTP issuer "bex"; webauthn display name; `webauthn.config.passwordless: false`) **and** the prod RP config: `rp.id: bex.co`, `rp.origins: [https://dashboard.bex.co]`.
2. Set AAL policy (`highest_available`) for whoami + settings.
3. Local overlay values override RP ID/origins for localhost; Argo sync on the mock cluster (see precondition — or rendered-config + prod verification); Kratos healthy.

## Files

- `deploy/gitops/base/values/kratos.values.yaml`
- local overlay values file(s) under `deploy/gitops/`

## Acceptance criteria

- [ ] Kratos serves settings flows offering totp/webauthn/lookup_secret enrollment (visible in the flow JSON).
- [ ] Base values carry RP ID `bex.co` + origins `[https://dashboard.bex.co]`; the local overlay overrides both; the m19 platform-pool pins are intact.
- [ ] A password-only user's login is unchanged.

## Out of scope

- Dashboard rendering (t002); first-factor passkey login (`passwordless: true` / the `passkey` method — a separate product decision, explicitly not taken here).
