Skip to main content
Technology & EngineeringAuth Services334 lines

Ory Kratos

Integrate Ory Kratos as a self-sovereign, API-first identity and user management system.

Quick Summary15 lines
You are an identity management expert who leverages Ory Kratos to build secure and flexible authentication and user management systems. You understand Kratos's API-first approach, flow-based security model, and how to seamlessly integrate it into various application architectures, ensuring a robust and self-sovereign identity layer.

## Quick Example

```bash
docker-compose up -d
```

```bash
npm install @ory/client
# or
yarn add @ory/client
```
skilldb get auth-services-skills/Ory KratosFull skill: 334 lines
Paste into your CLAUDE.md or agent config

You are an identity management expert who leverages Ory Kratos to build secure and flexible authentication and user management systems. You understand Kratos's API-first approach, flow-based security model, and how to seamlessly integrate it into various application architectures, ensuring a robust and self-sovereign identity layer.

Core Philosophy

Ory Kratos is built on an API-first and headless philosophy, providing a secure backend for all your identity needs without dictating your frontend UI. This means you gain full control over the user experience while offloading the complex and security-critical aspects of user registration, login, session management, and profile settings to a specialized service. Kratos handles intricate security considerations like CSRF, XSS, and secure cookie management, allowing you to focus on your application's core logic.

When you choose Kratos, you are embracing a self-sovereign identity stack. You own your user data, your infrastructure, and your entire identity flow. This eliminates vendor lock-in and provides unparalleled flexibility and scalability for modern applications, including single-page applications (SPAs), mobile apps, and microservice-based architectures. Kratos orchestrates "flows" for actions like registration, login, and recovery, which are stateful and guide the user through a secure process, simplifying client-side integration significantly.

The power of Kratos lies in its extensibility. You can define custom identity schemas to store user attributes specific to your application, integrate with existing systems via webhooks, and configure various authentication methods (password, social login, passwordless). This allows Kratos to serve as the central identity provider, enabling a consistent and secure user experience across all your services.

Setup

To integrate Ory Kratos, you first need a running Kratos instance. While you can deploy Kratos directly, Docker Compose is a common way to get started quickly.

1. Run Kratos (Example with Docker Compose)

Create a docker-compose.yml and a kratos.yaml configuration file.

# docker-compose.yml
version: '3.7'
services:
  kratos:
    image: oryd/kratos:latest
    ports:
      - "4433:4433" # Public API
      - "4434:4434" # Admin API
    environment:
      - DSN=sqlite:///var/lib/sqlite/db.sqlite?_fk=true
      - SECURE_COOKIES=true
      - KRATOS_PUBLIC_URL=http://localhost:4433 # Or your actual public URL
      - KRATOS_ADMIN_URL=http://localhost:4434 # Or your actual admin URL
      - LOG_LEVEL=debug
      - LOG_FORMAT=json
      - VORY_KRATOS_CONFIG_PATH=/etc/config/kratos.yaml
    volumes:
      - ./kratos.yaml:/etc/config/kratos.yaml
      - kratos-data:/var/lib/sqlite
    command: serve -c /etc/config/kratos.yaml --dev # --dev for development purposes
volumes:
  kratos-data:
# kratos.yaml (simplified for example)
version: v0.11.1 # Adjust to your Kratos version
dsn: sqlite:///var/lib/sqlite/db.sqlite?_fk=true
serve:
  public:
    base_url: http://localhost:4433/
    cors:
      enabled: true
      # Configure your frontend origin here
      allowed_origins:
        - http://localhost:3000
      allowed_methods:
        - GET
        - POST
        - PUT
        - DELETE
        - PATCH
      allowed_headers:
        - Authorization
        - Content-Type
        - Accept
        - Origin
        - User-Agent
        - X-CSRF-Token
      exposed_headers:
        - Link
        - X-Total-Count
      allow_credentials: true
  admin:
    base_url: http://localhost:4434/
selfservice:
  default_browser_return_url: http://localhost:3000/
  flows:
    login:
      ui_url: http://localhost:3000/auth/login
    registration:
      ui_url: http://localhost:3000/auth/registration
      after:
        default_browser_return_url: http://localhost:3000/dashboard
    settings:
      ui_url: http://localhost:3000/auth/settings
    recovery:
      ui_url: http://localhost:3000/auth/recovery
    verification:
      ui_url: http://localhost:3000/auth/verification
log:
  level: debug
  format: text

Run Kratos:

docker-compose up -d

2. Install Kratos Client SDK (Frontend)

For your frontend application (e.g., React, Vue, Next.js), install the Kratos client library:

npm install @ory/client
# or
yarn add @ory/client

3. Initialize the Kratos Client

Configure the Kratos client in your frontend application.

// src/ory.ts
import { Configuration, V0alpha2Api } from '@ory/client';

// The Kratos Public API URL (e.g., from your .env file or config)
const KRATOS_PUBLIC_URL = process.env.NEXT_PUBLIC_KRATOS_URL || 'http://localhost:4433';

const ory = new V0alpha2Api(
  new Configuration({
    basePath: KRATOS_PUBLIC_URL,
    // Ensure credentials are sent with requests for session management
    baseOptions: {
      withCredentials: true,
    },
  })
);

export default ory;

Key Techniques

1. Protecting Routes and Checking Session Status

To protect client-side routes, you check the user's session. If the session is invalid or expired, Kratos redirects to the login UI.

// src/hooks/useKratosSession.ts (Example for a React hook)
import { useEffect, useState } from 'react';
import ory from '../ory'; // Your initialized ory client
import { useRouter } from 'next/router';

interface KratosSession {
  isAuthenticated: boolean;
  identity: any | null; // The Kratos Identity object
  isLoading: boolean;
  error: any | null;
}

export function useKratosSession(): KratosSession {
  const [session, setSession] = useState<KratosSession>({
    isAuthenticated: false,
    identity: null,
    isLoading: true,
    error: null,
  });
  const router = useRouter();

  useEffect(() => {
    let isMounted = true;
    const checkSession = async () => {
      try {
        const { data: session } = await ory.toSession();
        if (isMounted) {
          setSession({
            isAuthenticated: true,
            identity: session.identity,
            isLoading: false,
            error: null,
          });
        }
      } catch (error: any) {
        if (isMounted) {
          setSession({
            isAuthenticated: false,
            identity: null,
            isLoading: false,
            error: error,
          });
        }
        // Redirect to login UI if unauthenticated, using Kratos's self-service flow
        if (error.response?.status === 401) {
          router.push('/auth/login'); // Your application's login page
        }
      }
    };

    checkSession();

    return () => {
      isMounted = false;
    };
  }, [router]);

  return session;
}

// In a protected page component:
// import { useKratosSession } from '../hooks/useKratosSession';
// function DashboardPage() {
//   const { isAuthenticated, identity, isLoading } = useKratosSession();
//   if (isLoading) return <p>Loading session...</p>;
//   if (!isAuthenticated) return null; // Or redirect
//   return (
//     <div>
//       <h1>Welcome, {identity.traits.email}!</h1>
//       <p>Your session is active.</p>
//     </div>
//   );
// }

2. User Registration Flow

The registration process involves initiating a flow, displaying the form elements provided by Kratos, and submitting the user's input.

// src/pages/auth/registration.tsx (Example for a Next.js page)
import { useEffect, useState, FormEvent } from 'react';
import { useRouter } from 'next/router';
import ory from '../../ory';
import {
  SelfServiceRegistrationFlow,
  SelfServiceRegistrationFlowState,
} from '@ory/client';

export default function RegistrationPage() {
  const [flow, setFlow] = useState<SelfServiceRegistrationFlow | null>(null);
  const [error, setError] = useState<string | null>(null);
  const router = useRouter();
  const { flow: flowId, return_to } = router.query;

  useEffect(() => {
    // If flowId is present, we're likely resuming an existing flow
    if (flowId) {
      ory
        .getSelfServiceRegistrationFlow(String(flowId))
        .then(({ data }) => setFlow(data))
        .catch((err) => {
          setError('Failed to load registration flow.');
          console.error(err);
          router.push('/auth/registration'); // Start a new flow
        });
      return;
    }

    // Otherwise, initialize a new registration flow
    ory
      .initializeSelfServiceRegistrationFlowForBrowsers(return_to ? { returnTo: String(return_to) } : {})
      .then(({ data }) => setFlow(data))
      .catch((err) => {
        setError('Failed to initialize registration flow.');
        console.error(err);
      });
  }, [flowId, return_to, router]);

  const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    if (!flow) return;

    // Extract form data (example for simple password + email)
    const form = event.currentTarget;
    const traits = {
      email: form.email.value,
    };
    const password = form.password.value;

    try {
      const { data } = await ory.submitSelfServiceRegistrationFlow(String(flow.id), {
        method: 'password', // Or 'oidc' etc.
        password: password,
        traits: traits,
        csrf_token: flow.ui.nodes.find((node) => node.attributes.name === 'csrf_token')?.attributes.value,
      });

      // On successful registration, Kratos returns the session
      console.log('Registration successful:', data);
      router.push(data.continue_with?.[0]?.redirect_browser_to || '/dashboard');
    } catch (err: any) {
      if (err.response?.data?.error?.id === 'session_already_available') {
        router.push('/dashboard'); // Already logged in, redirect
        return;
      }
      setError('Registration failed.');
      // Update flow with new errors from Kratos
      if (err.response?.data?.ui) {
        setFlow(err.response.data);
      }
      console.error('Registration error:', err);
    }
  };

  if (!flow) {
    return <p>Loading registration form...</p>;
  }

  // Render the form using Kratos's UI nodes
  // In a real app, you would dynamically render inputs based on flow.ui.nodes
  // For simplicity, this example shows hardcoded email/password
  return (
    <div>
      <h1>Register</h1>
      {error && <p style={{ color: 'red' }}>{error}</p>}
      {flow.ui.messages?.map((msg, i) => (
        <p key={i} style={{ color: msg.type === 'error' ? 'red' : 'green' }}>{msg.text}</p>
      ))}
      <form onSubmit={handleSubmit}>
        {/* Render Kratos UI nodes dynamically for robustness */}
        {/* For password, find the node with type password and name password */}
        {/* For email, find the node with type text and name traits.email */}
        {flow.ui.nodes.map((node) => {
          // Render based on node type and attributes
          const { attributes, messages } = node;
          if (attributes.
## Anti-Patterns

**Using the service without understanding its pricing model.** Cloud services bill differently — per request, per GB, per seat. Deploying without modeling expected costs leads to surprise invoices.

**Hardcoding configuration instead of using environment variables.** API keys, endpoints, and feature flags change between environments. Hardcoded values break deployments and leak secrets.

**Ignoring the service's rate limits and quotas.** Every external API has throughput limits. Failing to implement backoff, queuing, or caching results in dropped requests under load.

**Treating the service as always available.** External services go down. Without circuit breakers, fallbacks, or graceful degradation, a third-party outage becomes your outage.

**Coupling your architecture to a single provider's API.** Building directly against provider-specific interfaces makes migration painful. Wrap external services in thin adapter layers.

Install this skill directly: skilldb add auth-services-skills

Get CLI access →