Skip to main content
Ory Kratos implements a flow-based architecture for all user-facing operations. Each self-service operation (login, registration, recovery, verification, settings) follows a consistent flow pattern.

Flow-based architecture

Flows provide a structured, stateful way to handle complex user interactions with proper security, validation, and error handling.

Core flow interface

All flows implement the Flow interface defined in selfservice/flow/flow.go:38:
type Flow interface {
    GetID() uuid.UUID
    GetType() Type
    GetRequestURL() string
    AppendTo(*url.URL) *url.URL
    GetUI() *container.Container
    GetState() State
    SetState(State)
    GetFlowName() FlowName
    GetTransientPayload() json.RawMessage
}

Flow types

Kratos supports two flow types defined in selfservice/flow/type.go:11:
const (
    TypeAPI     Type = "api"
    TypeBrowser Type = "browser"
)

Browser flows

Browser flows are designed for traditional web applications with server-side rendering or cookie-based sessions.

Characteristics

  • CSRF protection - Anti-CSRF tokens embedded in forms
  • Cookie sessions - Session tokens stored in HTTP-only cookies
  • Redirects - Automatic redirects to UI and return URLs
  • Form-based submission - Standard HTML forms with Content-Type: application/x-www-form-urlencoded

Browser flow lifecycle

  1. Initiate flow
    GET /self-service/login/browser
    
    Kratos creates a flow and redirects to the UI URL with ?flow=<id>
  2. Render UI
    GET /login?flow=<id>
    
    UI application fetches flow data and renders the form
  3. Submit form
    POST /self-service/login?flow=<id>
    Content-Type: application/x-www-form-urlencoded
    
    csrf_token=...&[email protected]&password=...
    
  4. Process and redirect
    • On success: Redirect to return_to or default URL
    • On error: Redirect back to UI with error messages
Browser flows require cookies and JavaScript to be enabled. The CSRF token must be included in all form submissions.

Example: Login flow structure

From selfservice/flow/login/flow.go:41:
type Flow struct {
    ID             uuid.UUID     `json:"id"`
    Type           flow.Type     `json:"type"`
    ExpiresAt      time.Time     `json:"expires_at"`
    IssuedAt       time.Time     `json:"issued_at"`
    RequestURL     string        `json:"request_url"`
    Active         identity.CredentialsType `json:"active,omitempty"`
    UI             *container.Container `json:"ui"`
    CSRFToken      string        `json:"-"`
    Refresh        bool          `json:"refresh"`
    RequestedAAL   identity.AuthenticatorAssuranceLevel `json:"requested_aal"`
    State          State         `json:"state"`
}

API flows

API flows are designed for native mobile apps, SPAs, and machine-to-machine integrations.

Characteristics

  • No CSRF protection - Not needed for API clients
  • Token-based sessions - Session tokens returned in response body
  • JSON communication - Content-Type: application/json
  • No redirects - Responses contain flow state and data
  • Explicit flow management - Client controls flow lifecycle

API flow lifecycle

  1. Initialize flow
    POST /self-service/login/api
    
    Response:
    {
      "id": "<flow-id>",
      "type": "api",
      "expires_at": "2024-01-01T12:00:00Z",
      "ui": {
        "action": "/self-service/login?flow=<flow-id>",
        "method": "POST",
        "nodes": [...]
      }
    }
    
  2. Submit data
    POST /self-service/login?flow=<flow-id>
    Content-Type: application/json
    
    {
      "method": "password",
      "identifier": "[email protected]",
      "password": "secret"
    }
    
  3. Handle response
    • Success: Session token in response
    • Error: Updated flow with error messages
    • Requires action: Flow state indicates next step
API flows are stateless from the client perspective. Clients must include the flow ID in all requests.

Flow states

Flows transition through states as users progress. States are defined in selfservice/flow/state.go:31:
const (
    StateChooseMethod    State = "choose_method"
    StateEmailSent       State = "sent_email"
    StatePassedChallenge State = "passed_challenge"
    StateShowForm        State = "show_form"
    StateSuccess         State = "success"
)

State transitions

  1. choose_method - Initial state, user selects authentication method
  2. show_form - Display form for selected method (password, OIDC, etc.)
  3. success - Authentication successful, session created
  1. recovery_awaiting_address - User enters recovery address
  2. recovery_awaiting_address_choice - Multiple addresses found, user selects one
  3. recovery_awaiting_address_confirm - Confirming address ownership
  4. recovery_awaiting_code - User enters recovery code
  5. passed_challenge - Recovery successful

State management

func NextState(current State) State {
    if current == StatePassedChallenge {
        return StatePassedChallenge
    }
    return states[indexOf(current)+1]
}

Flow lifecycle

Creation

Flows are created with:
  • Unique ID - UUID v4 for identification
  • Expiration time - Configurable TTL (typically 1 hour)
  • Request URL - Original URL for context preservation
  • CSRF token - For browser flows only
  • UI container - Form structure with nodes

Expiration

Flows expire after a configured duration. Expired flows cannot be submitted:
func (f *Flow) Valid() error {
    if f.ExpiresAt.Before(time.Now()) {
        return errors.WithStack(flow.NewFlowExpiredError(f.ExpiresAt))
    }
    return nil
}
Expired flows must be discarded. Always create a new flow rather than reusing expired ones.

Internal context

Flows maintain internal state in InternalContext (not exposed to clients):
InternalContext sqlxx.JSONRawMessage `db:"internal_context" json:"-"`
Used for:
  • MFA challenges
  • Temporary codes
  • Flow-specific state
  • Strategy coordination

CSRF protection

Browser flows include CSRF protection to prevent cross-site attacks.

How it works

  1. Kratos generates a CSRF token when creating the flow
  2. Token is embedded in the UI container nodes
  3. UI includes token in form submission
  4. Kratos validates token before processing

Token structure

CSRFToken string `json:"-" db:"csrf_token"`
The token is:
  • Never exposed in JSON responses (note the json:"-" tag)
  • Only available when rendering the UI
  • Single-use per flow
  • Automatically validated by the framework
CSRF tokens are only used in browser flows. API flows do not require or validate CSRF tokens.

UI container

The UI container describes the form structure and is the same for both flow types.

Container structure

type Container struct {
    Action string      `json:"action"`
    Method string      `json:"method"`
    Nodes  Nodes       `json:"nodes"`
    Messages ui.Messages `json:"messages,omitempty"`
}

Nodes

Nodes represent form fields, buttons, scripts, and text:
{
  "nodes": [
    {
      "type": "input",
      "group": "password",
      "attributes": {
        "name": "identifier",
        "type": "text",
        "required": true,
        "disabled": false
      },
      "messages": [],
      "meta": {
        "label": {
          "id": 1070004,
          "text": "Email",
          "type": "info"
        }
      }
    },
    {
      "type": "input",
      "group": "password",
      "attributes": {
        "name": "password",
        "type": "password",
        "required": true
      },
      "messages": [],
      "meta": {
        "label": {
          "id": 1070001,
          "text": "Password",
          "type": "info"
        }
      }
    },
    {
      "type": "input",
      "group": "password",
      "attributes": {
        "name": "method",
        "type": "submit",
        "value": "password"
      },
      "messages": [],
      "meta": {
        "label": {
          "id": 1010001,
          "text": "Sign in",
          "type": "info"
        }
      }
    }
  ]
}

Flow types overview

Registration flow

Creates new identities with trait validation and optional verification. Endpoints:
  • Browser: GET /self-service/registration/browser
  • API: POST /self-service/registration/api
  • Submit: POST /self-service/registration?flow=<id>

Login flow

Authenticates existing identities and creates sessions. Endpoints:
  • Browser: GET /self-service/login/browser
  • API: POST /self-service/login/api
  • Submit: POST /self-service/login?flow=<id>
Features:
  • Multi-factor authentication (AAL2)
  • Forced re-authentication (?refresh=true)
  • AAL upgrade requests (?aal=aal2)

Recovery flow

Recovers access to accounts via recovery addresses. Endpoints:
  • Browser: GET /self-service/recovery/browser
  • API: POST /self-service/recovery/api
  • Submit: POST /self-service/recovery?flow=<id>

Verification flow

Verifies ownership of verifiable addresses (email, phone). Endpoints:
  • Browser: GET /self-service/verification/browser
  • API: POST /self-service/verification/api
  • Submit: POST /self-service/verification?flow=<id>

Settings flow

Allows users to update traits, passwords, and security settings. Endpoints:
  • Browser: GET /self-service/settings/browser
  • API: POST /self-service/settings/api
  • Submit: POST /self-service/settings?flow=<id>
The settings flow requires an active session. Users must be authenticated.

Flow strategies

Each flow supports multiple strategies (authentication methods):
  • password - Username/password authentication
  • oidc - OpenID Connect / OAuth2
  • webauthn - WebAuthn / passkeys
  • totp - Time-based one-time passwords
  • lookup_secret - Backup recovery codes
  • code - Email/SMS codes
  • link - Magic links
  • profile - Profile updates (settings only)
Strategies are enabled per flow in configuration:
selfservice:
  flows:
    login:
      enabled_strategies:
        - password
        - oidc
        - webauthn

Return URLs

Flows support return URL functionality for post-completion redirects.

Browser flows

Return URLs are validated against allowed domains:
selfservice:
  allowed_return_urls:
    - https://app.example.com
    - https://www.example.com

API flows

Return URLs can be included in flow initialization but are not automatically enforced. Clients handle navigation.

Error handling

Flow errors are handled gracefully:

Browser flows

  1. Error occurs during processing
  2. Flow is updated with error messages
  3. User redirected back to UI
  4. UI displays errors in context

API flows

  1. Error occurs during processing
  2. HTTP 400 response with updated flow
  3. Client parses errors from flow UI messages
  4. Client re-renders form with errors

Best practices

Flow selection
  • Use browser flows for traditional web apps
  • Use API flows for SPAs and mobile apps
  • Never mix browser and API flow patterns
Flow management
  • Always check flow expiration before rendering
  • Create new flows on expiration
  • Don’t cache flow IDs across sessions
  • Store flow IDs in URL parameters or temporary storage
Security
  • Always include CSRF tokens in browser flows
  • Validate return URLs against allowed domains
  • Use HTTPS in production
  • Implement rate limiting on flow endpoints
Never use the same flow ID for multiple submission attempts after success. Always create a new flow.

Build docs developers (and LLMs) love