Ory Kratos: The Auth Server That Refuses to Be Your Login Page

A deep dive into the headless identity engine behind custom UX, schema-driven identities, and flow-based authentication.

12 min read • View on GitHub • More from ory

A wide editorial scene shows a blank login doorway in the foreground while a mechanical identity engine turns behind it. A product team stands at a glass pane sketching their own interface, while flow arrows move through checkpoints marked login, recovery, MFA, and continuation. The image explains Kratos’s central tradeoff: it owns the dangerous identity machinery, but not the user-facing page.
Kratos gives you the engine, not the dashboard. That is the whole point.
Key Takeaways

Most auth products try to win by giving you less work. Kratos wins by taking away the wrong work. It removes the login page as a product decision, so your app can own the experience while the backend absorbs the security risk.

That sounds austere until you look at the shape of the repo. Kratos is not a generic CRUD service with a couple of endpoints bolted on. It is a choreography engine for identity, built around flows, resumable state, and delivery that can survive the messy parts of the real world.

We started Ory Kratos because we saw so many developers struggle with OAuth2 and OpenID Connect.

The login box is the product, except Kratos deletes it

Kratos makes a blunt bet: the UI should belong to the application, not the identity layer. That means no bundled login experience to fight, no theme to work around, and no hidden assumptions about how your brand or routing should behave.

The upside is control. The downside is that you have to build the front end yourself. Kratos takes that hit on purpose because the risky parts are not the page chrome, they are the rules around credentials, recovery, verification, and session handling.

This is why the project feels more like infrastructure than a library. It is designed to stay out of your way visually while staying in the path of every dangerous transition.

Identity is a flow, not a form

The important unit is not a page. It is a resumable flow that survives redirects and external checks.

This is Kratos’s most important idea. Login, registration, recovery, and MFA are not one-off requests. They are flows that can pause, hand work to another system, and resume later without losing their place.

That is what continuity exists to protect. A browser redirect, an email click, or a social sign-in callback should not force the system to forget who started the flow or what state it was in. The continuity layer keeps that context alive while still resisting session fixation and other identity mix-ups.

A close-up editorial scene shows a narrow corridor split into three chambers. A user begins a login step in the first chamber, pauses at an email checkpoint in the middle, and resumes in the last chamber through a continuity capsule. A small lock sits beside the passage to suggest that state is preserved without allowing identity confusion. The image explains how Kratos can safely pause and resume flows across redirects.
A Kratos flow can stop, wait, and come back without becoming a different flow.

Why Kratos does not use a classic users table

A traditional identity system wants fixed columns. Email here. Password there. Maybe a few more fields if the data model gets ambitious. Kratos goes the other direction and treats identity traits as schema-driven data, which is far more tolerant of different user shapes over time.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "identity",
  "type": "object",
  "properties": {
    "traits": {
      "type": "object",
      "properties": {
        "email": { "type": "string", "format": "email" },
        "name": { "type": "object" },
        "company": { "type": "string" }
      },
      "required": ["email"]
    }
  }
}

The point is not that JSON Schema is trendy. The point is that it lets identity evolve without turning schema migrations into the bottleneck for every new user property. That matters when one product team wants a consumer profile and another wants B2B attributes, but both still need the same authentication backbone.

ApproachIdentity shapeChange managementTradeoff
Classic users tableFixed columnsALTER TABLE becomes the growth pathSimple at first, brittle later
Kratos schema-driven traitsValidated JSON traitsSchema changes are first-classMore flexible, less rigid
Ad hoc app-owned authWhatever the app inventsEvery team reinvents migrationsMaximum freedom, maximum drift

The same pattern shows up in the codebase. Kratos isolates dynamic mapping instead of letting it leak into the core system. That keeps identity logic predictable while still allowing external profile data to be transformed when it needs to be.

The courier is where auth gets real

Identity is never just a request-response loop. It has to send codes, links, and alerts into systems that may be slow, flaky, or temporarily unavailable. Kratos handles that with a courier subsystem that behaves more like durable infrastructure than a helper function.

Messages are queued, retried, and eventually marked sent or abandoned. The important detail is that delivery is asynchronous, which means auth flows do not block on the mail server feeling cooperative.

We needed a new system to import PBKDF2-hashed passwords and this disqualified AWS Cognito.

SawadaShota, Developer at TIER IV, Ory Guest · How and why TIER IV replaced IdentityServer with Ory

Why the repository feels like a production system, not a library

The repo structure tells you a lot. There is a registry pattern for dependency injection, separate public and admin surfaces, a dedicated continuity layer, and a courier that gets its own operational logic. That is not how a small package is organized. It is how a service meant to live inside production traffic is organized.

The split between public and admin APIs is especially revealing. Public endpoints need CSRF protection, browser safety, and user-facing ergonomics. Admin operations need different assumptions entirely. Kratos keeps those worlds apart instead of pretending they are the same thing.

It also pays attention to the boring but important stuff: hermetic builds, database portability, process isolation for Jsonnet execution, and observability hooks. That is what makes the project feel durable rather than merely clever.

Where Kratos wins, and where it deliberately does not

Kratos is not trying to be every identity product at once. It is narrower than Auth0 or Okta, more specialized than an all-in-one IAM suite, and more opinionated than a homegrown auth stack that lives inside your application code.

SystemWhat you getWhat you give upBest fit
Auth0 / OktaManaged identity with bundled UXLess control over the full experienceTeams that want speed over ownership
KeycloakBroad all-in-one IAMHeavier surface area and more moving partsOrganizations that want a self-hosted suite
Homegrown authTotal flexibilityYou own every edge case and every bugSmall systems with unusual constraints
Ory KratosHeadless, schema-driven, flow-based authYou build the UI and own the integrationTeams that want custom UX with hardened backend primitives

The tradeoff is especially clear around multi-tenancy. Kratos does not pretend every deployment wants to model multiple organizations inside one identity store. It favors cleaner boundaries, separate instances when needed, and a narrower definition of what the product should own.

That restraint is part of the design. Kratos is compelling precisely because it refuses to become a bloated identity suite when a specialized engine will do.