MADORIMADORI

Authentication

Madori includes a complete authentication system with file-based users, session management, and role-based permissions. The system is pluggable — you can swap out the credential validation, session storage, or user provider without changing application code.

Authentication protects the Control Panel and API endpoints. Sessions are managed via cookies (browser) or Bearer tokens (API clients).


Configuration Reference

Auth Configuration

Authentication is configured in madori.config.ts:

Option Type Default Description
auth.driver string password Authentication driver — validates credentials
auth.store string file Session storage backend
auth.provider string yaml User data provider
auth.storeConfig.sessionsDir string ./.sessions Directory for session files
auth.storeConfig.sessionDurationMs number 86400000 (24h) Session expiry duration in milliseconds
// madori.config.ts
auth: {
  driver: 'password',
  store: 'file',
  provider: 'yaml',
  storeConfig: {
    sessionsDir: './.sessions',
    sessionDurationMs: 86400000, // 24 hours
  },
}

User Storage

Users are stored as YAML files in the directory specified by usersPath (default: ./users/):

# users/admin.yaml
id: admin
email: [email protected]
name: Admin
password_hash: $2b$10$...
roles:
  - admin
created_at: 2026-01-01T00:00:00.000Z
Property Type Required Description
id string Yes Unique user identifier (matches filename)
email string Yes Login email address (must be unique)
name string Yes Display name
password_hash string Yes bcrypt-hashed password
roles string[] No Array of role handles assigned to this user
created_at string No ISO 8601 creation timestamp

Session Management

Sessions use cryptographically random tokens stored as JSON files (SHA-256 hashed filename). Authentication supports two methods:

Method Header/Cookie Description
Cookie madori_session httpOnly, secure in production. Used by browser-based CP
Bearer token Authorization: Bearer {token} Used by API clients and scripts

For Control Panel page requests, the Next.js Proxy performs an optimistic cookie-presence check. It does not call an internal HTTP endpoint or session store. Protected API handlers validate the session token and permissions before reading or changing data. An expired or invalid cookie therefore cannot authorize API access; a 401 response sends the browser back to /cp/login.

This split keeps route checks fast and avoids loopback HTTP requests when Madori runs behind Nginx, Cloudflare, or another SSL-terminating reverse proxy.

Roles and Permissions

Roles are defined as YAML files at resources/roles/{handle}.yaml:

# resources/roles/admin.yaml
handle: admin
display: Administrator
permissions:
  - resource: collections
    actions: [view, create, edit, delete, publish]
  - resource: entries
    actions: [view, create, edit, delete, publish]
  - resource: assets
    actions: [view, create, edit, delete]
  - resource: users
    actions: [view, create, edit, delete]
  - resource: globals
    actions: [view, edit]
  - resource: taxonomies
    actions: [view, create, edit, delete]
  - resource: forms
    actions: [view, create, edit, delete]
  - resource: navigation
    actions: [view, create, edit, delete]
  - resource: settings
    actions: [view, edit]

Available Resources

Resource Description
collections Collection definitions and blueprints
entries Content entries
assets Uploaded files
users User accounts
globals Global data sets
taxonomies Taxonomy definitions and terms
forms Form definitions and submissions
navigation Navigation structures
settings System settings

Available Actions

Action Description
view Read access
create Create new items
edit Modify existing items
delete Remove items
publish Change status to published

API Endpoints

Method Endpoint Auth Required Description
POST /api/auth/login No Login with email/password
POST /api/auth/logout Yes Destroy session
GET /api/auth/validate No Check if token is valid
GET /api/users Yes List all users
POST /api/users Yes Create user
GET /api/users/{id} Yes Get user
PUT /api/users/{id} Yes Update user
DELETE /api/users/{id} Yes Delete user

Usage Examples

Creating Users via CLI

The simplest way to create a user:

pnpm madori make:user

This prompts for email, name, password, and roles interactively.

Creating Users via the Control Panel

  1. Navigate to Users in the CP sidebar
  2. Click Create User
  3. Fill in email, name, password, and assign roles
  4. Save

Creating Users via API

await fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <admin-token>',
  },
  body: JSON.stringify({
    email: '[email protected]',
    name: 'Jane Editor',
    password: 'secure-password',
    roles: ['editor'],
  }),
})

Logging In via API

const response = await fetch('/api/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    email: '[email protected]',
    password: 'password',
  }),
})

const { token } = await response.json()
// Use token in subsequent requests:
// Authorization: Bearer <token>

Validating a Session

const response = await fetch('/api/auth/validate', {
  headers: {
    'Authorization': 'Bearer <token>',
  },
})

if (response.ok) {
  const { user } = await response.json()
  // Session is valid, user object contains id, email, name, roles
}

Defining a Custom Role

Create a restricted editor role that can only manage content:

# resources/roles/editor.yaml
handle: editor
display: Editor
permissions:
  - resource: entries
    actions: [view, create, edit, publish]
  - resource: assets
    actions: [view, create, edit]
  - resource: globals
    actions: [view, edit]
  - resource: forms
    actions: [view]
  - resource: navigation
    actions: [view, edit]

Logging Out

await fetch('/api/auth/logout', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer <token>',
  },
})

Common Patterns

Role Hierarchy

Define roles with increasing permissions for different team members:

resources/roles/
├── admin.yaml       # Full access to everything
├── editor.yaml      # Content + assets, no users/settings
├── author.yaml      # Create/edit own entries only
└── viewer.yaml      # Read-only access

Read-Only Viewer Role

For stakeholders who need to see content but not modify it:

# resources/roles/viewer.yaml
handle: viewer
display: Viewer
permissions:
  - resource: entries
    actions: [view]
  - resource: assets
    actions: [view]
  - resource: globals
    actions: [view]
  - resource: forms
    actions: [view]
  - resource: navigation
    actions: [view]

Extending Session Duration

For internal teams where frequent re-login is disruptive:

// madori.config.ts
auth: {
  driver: 'password',
  store: 'file',
  provider: 'yaml',
  storeConfig: {
    sessionsDir: './.sessions',
    sessionDurationMs: 7 * 24 * 60 * 60 * 1000, // 7 days
  },
}

Securing Production

For production environments, tighten session duration:

auth: {
  storeConfig: {
    sessionDurationMs: 4 * 60 * 60 * 1000, // 4 hours
  },
}

Custom Authentication Drivers

The auth system uses an adapter pattern with three pluggable contracts:

  • AuthDriver — validates credentials (default: bcrypt password comparison)
  • SessionStore — manages session tokens (default: file-based JSON)
  • UserProvider — reads/writes user data (default: YAML files)

Implement custom adapters to integrate with external identity providers, databases, or SSO systems.

Git-Ignoring Sensitive Files

Add these to .gitignore to avoid committing sensitive data:

.sessions/
users/

For team environments where users should be consistent across clones, you may choose to commit users/ but ensure password hashes are strong.

Multi-Environment User Setup

Create environment-specific user files:

# Development — relaxed password for convenience
users/dev-admin.yaml

# Staging — real credentials
users/staging-admin.yaml

# Production — managed separately, never committed

Use the CLI to create users per environment:

pnpm madori make:user