Skip to content

docs(auth): document Built-in IdP MFA (TOTP) feature#148

Open
k1LoW wants to merge 4 commits into
mainfrom
idp-mfa
Open

docs(auth): document Built-in IdP MFA (TOTP) feature#148
k1LoW wants to merge 4 commits into
mainfrom
idp-mfa

Conversation

@k1LoW

@k1LoW k1LoW commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

The Built-in IdP now supports TOTP-based multi-factor authentication (tailor-platform/sdk#1541, #1561, #1562), but the user-facing guides under docs/guides/ did not yet describe it. This documents the consumer-facing behavior so app developers can enable, enforce, and self-service MFA without having to read the SDK reference.

Changes

  • docs/guides/auth/integration/built-in-idp.md — add a Multi-Factor Authentication (TOTP) section under userAuthPolicy. Splits the two flows that are easy to conflate:

    • Mandatory enrollment during sign-in (requireMfa: true) — the IdP /signin page handles QR + verify inline before completing the OIDC callback. No application-side code is needed to bootstrap enrollment.
    • Self-service management (enableMfa: true)_requestMfaSettingsUrl issues an MFA settings page URL bound to the calling user; the app redirects them to it to add or remove their own factors.

    Documents enableMfa / requireMfa / allowedReturnOrigins / mfaIssuer, the cross-field constraints, the permission.unenrollMfa requirement, and the two new GraphQL operations plus the mfaEnrolled / mfaFactorIds fields on the User type. Also extends the User Management operation list to include _requestMfaSettingsUrl and _unenrollMfa.

  • docs/guides/function/managing-idp-users.md — extend the runtime tailor.idp.Client interface block with User.mfaEnrolled, User.mfaFactorIds, UnenrollMfaInput, and client.unenrollMfa(), and add an Unenroll MFA Factor section showing the typical admin recovery flow.

docs/sdk/services/idp.md is intentionally not edited here. It is auto-synced from the SDK repo by the sdk-docs-sync workflow, and the SDK-side page already covers the SDK-shaped surface (gqlOperations MFA fields, schema constraints, runtime API).

Verification

  • pnpm build ✅ (pages render, the new #multi-factor-authentication-totp anchor resolves)
  • pnpm lint ✅ (no new issues)
  • npx tsx scripts/validate-schema.mts
  • Manually verified against tailor-platform/sdk@main (packages/sdk/src/runtime/idp.ts, packages/sdk/src/parser/service/idp/schema.ts) — the requireMfa: true flow enrolls users inline at /signin, so the doc reflects that rather than directing apps to call _requestMfaSettingsUrl for initial enrollment.

The Built-in IdP now supports TOTP-based multi-factor authentication
(tailor-platform/sdk#1541, #1561, #1562; platform-core-services MFA work
through #12233 / #12257). The user-facing guides under `docs/guides/`
did not reflect the new surface, so app developers had no way to learn
how to enable, enforce, or self-service MFA without reading the SDK
reference page.

This documents the consumer-facing behavior in the two guides that are
docs-repo-native:

- `docs/guides/auth/integration/built-in-idp.md` — add a Multi-Factor
  Authentication (TOTP) section under userAuthPolicy, splitting the two
  flows that are easy to conflate: (a) inline enrollment that the IdP
  /signin page handles itself when `requireMfa: true`, and (b)
  self-service factor management via `_requestMfaSettingsUrl`, which
  issues a per-user MFA settings page URL. Documents `enableMfa`,
  `requireMfa`, `allowedReturnOrigins`, `mfaIssuer`, the cross-field
  constraints, the `permission.unenrollMfa` requirement, and the two
  new GraphQL operations plus the `mfaEnrolled` / `mfaFactorIds` fields
  on the User type. Also extends the User Management op list.

- `docs/guides/function/managing-idp-users.md` — extend the runtime
  `tailor.idp.Client` interface block with `User.mfaEnrolled`,
  `User.mfaFactorIds`, `UnenrollMfaInput`, and `client.unenrollMfa()`,
  plus an Unenroll MFA Factor section showing the typical admin
  recovery flow.

`docs/sdk/services/idp.md` is intentionally not edited here — it is
auto-synced from the SDK repo by the `sdk-docs-sync` workflow, and the
SDK-side page already covers the SDK-shaped surface (gqlOperations
MFA fields, schema constraints, runtime API).
@k1LoW k1LoW self-assigned this Jun 26, 2026
@k1LoW k1LoW requested a review from Copilot June 26, 2026 01:09

This comment was marked as outdated.

Fix two issues raised in Copilot review on #148:

- built-in-idp.md: the `requireMfa` option description said unenrolled
  users must complete enrollment "via the MFA settings page", which
  contradicts the inline /signin enrollment flow described two
  paragraphs above. Rewrite to point at the inline flow and note that
  the application does not need to call _requestMfaSettingsUrl to
  bootstrap enrollment.
- managing-idp-users.md: note that `unenrollMfa` additionally requires
  read access to the target user (enforced at the dataplane RPC,
  mirroring the built-in-idp guide), so a namespace that denies `read`
  cannot use `unenrollMfa` either.

This comment was marked as outdated.

…ports

Address Copilot review on c288b80:

- managing-idp-users.md: prose said `unenrollMfa` removes a single
  factor while the only example unenrolled every factor via
  `Promise.all`, which a copy/paste would reproduce by mistake. Lead
  with the single-factor case (the API's actual unit) and present the
  full-reset loop as an explicit second example for the lost-device
  recovery flow. Pluralize the "no enrolled MFA factors" reason
  string while there.
- built-in-idp.md: drop unused imports (`defineConfig`, `defineAuth`,
  `user`) from the MFA configuration snippet so the imports actually
  match what the snippet declares.

This comment was marked as outdated.

Address Copilot review on 979a5b0: `Client.unenrollMfa()` returns
`Promise<boolean>`, but both new examples discarded the result and
returned `{ success: true }` unconditionally. That breaks the
convention established by `deleteUser` / `sendPasswordResetEmail`
elsewhere in this guide and would mask a false return from the API.

- Single-factor example now captures and returns the boolean
  (`const success = await idpClient.unenrollMfa(...); return { success }`).
- Reset-all example collects the per-factor booleans via
  `Promise.all` and aggregates with `results.every(Boolean)`.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@k1LoW k1LoW marked this pull request as ready for review June 26, 2026 01:36
@k1LoW k1LoW requested a review from a team as a code owner June 26, 2026 01:36
@k1LoW k1LoW added the documentation Improvements or additions to documentation label Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants