Configuring Ory Kratos for Email and Password Self-Service Registration
Enable email+password registration and login in Ory Kratos using a single identity schema, browser flow configuration, and SMTP verification. Includes a worked kratos.yml example and common pitfalls.
20 Mar 2026, 16:49 UTC

The practical decision: use built-in flows, not a custom store
If you need email-and-password sign-up and sign-in in Ory Kratos, the supported path is to enable the built-in self-service registration and login browser flows with a single identity schema and SMTP verification. You do not need to build a custom identity store or manage credential tables yourself. The main engineering decisions are: mark the email trait correctly in the identity schema so Kratos can use it as the password identifier and verifiable address, allow-list the exact return URLs your frontend uses, and configure the SMTP courier so verification mail actually leaves the server.
One identity schema and one kratos.yml
Kratos stores each identity as a JSON document validated by your schema. The browser flow renders form fields from the schema traits. Mark the email trait with the ory.sh/kratos extensions credentials.password.identifier and verification.via=email, and require it. Without those extensions, login cannot find the user by email and verification mail is never sent.
Identity schema (identity.schema.json)
{
"$id": "user",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "User",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
"ory.sh/kratos": {
"credentials.password.identifier": true,
"verification.via": "email"
}
}
},
"required": ["email"]
}
}
}
Minimal Kratos configuration (kratos.yml)
dsn: postgres://user:pass@host:5432/db
serve:
public:
base_url: http://127.0.0.1:4433
admin:
base_url: http://127.0.0.1:4434
identity:
default_schema_id: user
schemas:
- id: user
url: file:///etc/kratos/identity.schema.json
selfservice:
default_browser_return_url: http://127.0.0.1:3000/
allowed_return_urls:
- http://127.0.0.1:3000
flows:
registration:
enabled: true
ui_url: http://127.0.0.1:3000/registration
login:
enabled: true
ui_url: http://127.0.0.1:3000/login
courier:
smtp:
connection_uri: smtps://user:pass@smtp:465
from_address: [contact removed]
Run the SQL migrations before starting Kratos, and keep serve.admin.base_url on a private network. The admin API and the database DSN must never be exposed publicly. Treat the SMTP connection URI as a secret and rotate credentials when needed.
How the browser flow works
When a user visits /self-service/registration/browser, Kratos creates a flow and returns a form definition whose ui.nodes match the traits in your schema. Posting the form to the same endpoint creates the identity. After hooks then either start a session immediately or require email verification before the user can sign in. The session cookie is issued only after any required verification step completes. The login flow at /self-service/login/browser works the same way: it renders nodes for the password identifier trait and issues a session on success.
Limits and common mistakes
- Flows expire. Browser and API flows default to roughly one hour. A stale flow ID returns an error; start a new flow rather than retrying the old one.
- Browser and API flows are not interchangeable. Do not call the API flow from a browser or expect a browser flow to return JSON for a native app. Use
/self-service/registration/browserfor web apps and/self-service/registration/apifor native or machine clients. - Return URLs must be allow-listed. Omitting
selfservice.allowed_return_urlsordefault_browser_return_urlcauses redirect failures after registration or login. - SMTP misconfiguration fails silently. If the courier cannot connect or the from address is rejected, Kratos may log an error but the user never receives mail. Test with a real mailbox before relying on verification.
- Version sensitivity. Config keys, hook names, and defaults have changed across Kratos major versions. The
ory.sh/kratosextensions and after-hook behavior differ between v0.x and v1.x; do not assume a hook logs the user in when verification is required. Confirm exact YAML against your release. - Unverified addresses block login. If verification is required, the identity remains unverified until the user clicks the link. Check the identity's verifiable address state before debugging password logic.
Checking the result
- Start Kratos with the config above and run the SQL migration. Check
GET /health/aliveon the public port. - Fetch
GET /self-service/registration/browserand confirmui.nodesinclude the email and password fields from your schema. - Submit the form and inspect the created identity via the admin API or the Kratos CLI (for example,
kratos list identities). - Watch the Kratos logs for courier errors and confirm the verification email arrives. If login is blocked, check whether the identity's verifiable address is still unverified.
- Re-test after changing
allowed_return_urlsor cookie settings, since redirect and session behavior are the usual failure points.
This configuration is a starting point. Rate limiting, CSRF/anti-bot protections, and custom templates are separate concerns that you layer on once the base flow works.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.