Passwordless Login and Email Verification with Ory Kratos: What the Config Actually Looks Like
How Ory Kratos' code method and verification flow enable passwordless, email-verified authentication — with honest config pointers, verification steps, and the trade-offs that matter.
26 Sept 2026, 09:06 UTC

The problem: passwords are friction you may not need
If your application only needs to know that a user controls an email address, requiring a password at sign-up adds a step users abandon, a credential you must protect, and a reset flow you must maintain. Ory Kratos supports an alternative: verify the email address during registration, then let users sign in with a one-time code sent by email — no password stored at all.
The catch is that Kratos is configured through a fairly dense YAML schema, and it is easy to guess at keys that do not exist. This piece walks through the parts of the configuration that are actually responsible for this flow, based on Kratos' documented self-service model, and flags the points you should verify against the version you run.
How Kratos models this flow
Kratos separates methods (how a credential works) from flows (registration, login, verification, recovery). Two pieces matter here:
- The
codemethod for login and registration sends a one-time code (or link, depending on configuration) to the user's email address. Submitting the correct code completes the flow. This is the passwordless mechanism. - The verification flow confirms ownership of an address. When an identity's traits include an email marked as verifiable in the identity schema, Kratos can require verification before the address is trusted.
The identity schema is the real gatekeeper: whether passwordless works depends on which credentials the schema allows. If you remove password from the enabled methods and rely on code, identities are created without a password credential.
Configuration sketch (verify against your version)
The relevant areas of kratos.yml — note that exact key names have changed across Kratos releases, so treat this as a map of where to look, and confirm against the config reference for your version:
selfservice:
methods:
code:
enabled: true
passwordless_enabled: true
link:
enabled: true # enables link-based verification/recovery
password:
enabled: false # omit passwords entirely
flows:
verification:
enabled: true
use: code # or link
login:
lifespan: 10m
courier:
smtp:
connection_uri: smtp://user:[contact removed]:587/
Key points:
courieris a top-level section, not nested underselfservice. A mis-placed courier block is a common cause of "no email ever arrives."- Flow lifespans (how long a flow or its code stays valid) live under
selfservice.flows.<flow>.lifespan. There is no per-stepttlkey; do not invent one — Kratos rejects unknown config keys at startup, which is actually a useful validation signal. - The identity schema must mark the email trait as a verification/recovery address (via Kratos' schema extensions, e.g.
ory.sh/kratosannotations) for the verification flow to target it.
Because these keys have shifted between releases, check kratos help output and the config schema shipped with your binary rather than trusting any blog snippet — including this one — blindly.
Verifying the flow locally
A practical way to confirm behavior without inventing expectations:
- Run Kratos locally (the Ory docs publish a quickstart; pin a specific image tag rather than
latestso your results are reproducible). You need permission to run Docker or the Kratos binary. - Point the courier at a local mail catcher such as Mailpit or MailHog so you can inspect outgoing mail without a real SMTP account.
- Initiate a registration flow via the public API or UI, submit an email, and confirm a message arrives in the catcher containing a code or link.
- Complete the flow, then query the identity admin endpoint to confirm the identity exists with a verified address and no password credential.
- Start a login flow with the same address, retrieve the code from the catcher, submit it, and confirm a session is issued (session cookie in a browser flow, or session token for native apps).
Do not script assertions against specific log-line wording — log messages are not a stable API and change between versions. Assert on observable outcomes instead: email delivered, flow state transitions, identity verified, session created.
Trade-offs worth deciding explicitly
Code vs. link: A numeric code the user types in avoids the "link opened in a different browser than the flow" problem, which breaks cookie-based flows. A link is smoother when the user stays on one device. Kratos supports both patterns; pick based on where your users read email.
Lifespan: Shorter flow lifespans shrink the replay window but punish slow email delivery. There is no universally right value; start with the default and tighten only if you have a concrete threat model requiring it.
Deliverability is a hard dependency: If SMTP fails, users cannot register or log in at all — there is no password fallback if you disabled it. Monitor courier errors and consider keeping the password method enabled during a migration period.
Actionable closing
Start from the identity schema, not the flow config: decide whether passwords exist at all, then enable the code method and the verification flow, configure the top-level courier, and validate the whole flow end-to-end against a mail catcher before touching production. Pin your Kratos version, read its bundled config schema, and treat any YAML you did not verify against that schema as a draft, not a fact.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.