Decoupling Identity: Managing User Flows with Ory Kratos
Learn how Ory Kratos decouples identity management from application logic using a headless architecture, state-machine flows, and JSON schemas.
03 Jun 2026, 09:28 UTC

The Cost of Custom Identity Logic
Most applications treat user registration, password resets, and profile updates as a set of custom backend routes. This approach leads to a fragmented codebase where identity logic—validation, session state, and security constraints—is scattered across the application. When business requirements change (e.g., adding a required phone number or changing password complexity), developers must modify the database schema, the API validation logic, and the frontend forms simultaneously.
The takeaway is that identity management should be treated as a state machine, not a series of endpoints. By using a headless identity server like Ory Kratos, you move the state of the identity flow (where the user is in the registration process) out of your application and into a dedicated service.
Headless Architecture and the Flow ID
Ory Kratos operates as a "headless" server. Unlike traditional identity providers that force you to redirect users to a hosted login page, Kratos provides the logic via an API and leaves the UI entirely to you. This is achieved through Flows.
A Flow is a stateful sequence of steps. When a user clicks "Sign Up," your application requests a registration flow from Kratos. Kratos returns a flow_id. This ID acts as a pointer to the current state of the user's progress. Your UI then renders the fields required for that specific state. If the user submits a form with an invalid email, Kratos doesn't just return an error; it updates the flow state and tells the UI exactly which fields failed validation.
Defining Identity via JSON Schema
To avoid modifying the core binary every time you need a new user attribute, Kratos uses an Identity Schema. This is a JSON Schema file that defines what a "user" looks like in your system.
By decoupling the schema from the engine, you can define custom traits—such as a company ID or a subscription tier—without writing new database migrations in your primary application. Kratos handles the validation of these traits automatically based on the schema definitions.
Example: Custom Identity Configuration
To add a required "username" and an optional "company" field to your users, you would configure your identity.schema.json as follows:
{
"$id": "https://example.com/identity.schema.json",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"username": {
"type": "string",
"title": "Username",
"fmt": "username"
},
"company": {
"type": "string",
"title": "Company Name"
}
},
"required": ["username"]
}
}
}
Implementation Note: This file is loaded by the Kratos server during startup. To apply changes, you must restart the Kratos instance. If you define a field as required in the schema, Kratos will reject any registration attempt that omits that field, returning a 400 error with a detailed JSON body explaining the validation failure.
The Trade-off: UI Effort vs. Control
The primary limitation of the headless approach is the UI burden. Because Kratos does not provide a default frontend, you are responsible for building every single form: login, registration, settings, and recovery.
While this provides total control over the user experience (no "Powered by X" branding or jarring redirects), it increases the initial development time. You must map the JSON error responses from the Kratos API to your UI components to ensure users see helpful validation messages.
Verifying the Integration
To verify that your identity flow is working correctly, you can use curl to interact with the Public API. Run these commands from a terminal with network access to your Kratos instance:
- Initialize a Flow: Request a registration flow to get a
flow_id.curl -X GET "http:///self-service/registration/browser" - Check the Response: Look for the
flowobject in the JSON response. Ensure theui_nodesmatch the traits defined in your JSON schema. - Submit Data: Send a POST request to the flow URL provided in the previous step with your user traits. If successful, Kratos will set a session cookie in the response headers.
Risk: Be cautious with cookie domains. If your Kratos API is on auth.example.com and your app is on app.example.com, ensure your cookie configuration allows for cross-subdomain session sharing, otherwise users will be logged out immediately after a successful flow.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.