Skip to content

Phase B — Email + passkey signup #111

Description

@yahyafakhroji

Sub-issue of datum-cloud/enhancements#738. Phase B of the passkey program — this is the original ask on that issue.

Scope

A signup path that needs no social IdP and no password: user gives an email, verifies
it, then enrolls a passkey. Email must be verified before the account can act on the
platform (hard gate). Phase B reuses every ceremony and management surface built in
Phase A and adds the risky surface: backend-sent verification email, the hard gate,
enumeration safety, and bot protection.

What's included

Email pipeline (longest cross-repo path — starts before the rest of Phase B)

  • Zitadel HTTP email provider: create + activate, signing key into the milo receiver config (infra)
  • Notification webhook receiver: verify signing key, ack-then-persist, map event type to an EmailTemplate CR, alert on unmapped types (milo)
  • emails/user-email-verification.tsx, link-preferred over raw code (email-templates)
  • TTL/GC for pre-signup verification Email CRs + validate delivery latency for OTP-grade UX (milo)

Signup rework (auth-ui)

  • Email-verify-first ordering; retire create-user-before-ceremony and GC abandoned factorless accounts; migrate to CreateUser
  • Preserve the enumeration-safety invariant byte-for-byte
  • Flag hygiene: decouple showPasskey from AUTH_EMAIL_DELIVERY_ENABLED; promote EMAIL_VERIFICATION into the env schema

Hard gate (milo, zitadel-provider, cloud-portal)

  • email_verified synced onto the milo User + authn claim validation + authz-webhook backstop
  • Portal: blocking verify-email route + fraud-redirect branch, fail-closed
  • Provisioning parity: org/membership/quota identical to social signup; welcome-email parity

Enablement (infra)

  • Per-env flips: disableLoginWithEmail, AUTH_EMAIL_DELIVERY_ENABLED, EMAIL_VERIFICATION — staging then production

Passkey-removed notification (moved from Phase A, 31.07.26)

  • user-passkey-removed.tsx template with optional PasskeyName (email-templates)
  • ListUserMetadata on pkg/zitadel + passkey_removed.go handler (zitadel-provider)
  • auth-ui persists the passkey name in the created-at metadata value and tombstones the key on removal
  • Target + Execution bound to user.human.passwordless.token.removed, staging and production (infra)
  • Drop the unsourceable Browser/Device variables from the passkey-added contract

Design and implementation plan are already written and approved — the removal event carries
no passkey name, so auth-ui must persist it at enroll time. Deferred from Phase A because it
spans four repos.

Status

Not started. Phase A had to land first: the shared WebAuthn plumbing, the management
surface, and the org-policy flip are all prerequisites, and shipping the reversible thing
first is the correct risk ordering.

Dependencies and gates

  • Blocked on Phase A being live (done on staging).
  • The email-templates → OCI bundle → Flux pipeline is the longest lead time in the program: ship templates before the code referencing them.
  • GA is gated on: Phase C recovery design sign-off, a bot-protection tooling decision, and enumeration tests passing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions