# Understanding the ai-memory Four-Rung Auth Ladder: From Root to DB-User Tokens

> Explore the ai-memory four-rung auth ladder, a hierarchical system from Anonymous to DB User tokens, ensuring secure request resolution. Learn how it works.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: deep-dive
- Published: 2026-08-27

---

**The ai-memory four-rung auth ladder is a hierarchical authentication system that resolves every HTTP request to one of four tiers—Anonymous, Root, Proxy-Asserted User, or DB User—where the first matching credential wins and unknown tokens result in immediate 401 rejection.**

The ai-memory project by akitaonrails implements a strict, ordered authentication hierarchy to handle everything from unauthenticated reads to administrative operations. This four-rung auth ladder evaluates credentials sequentially, ensuring that requests receive exactly the privileges they claim without implicit escalation.

## What Is the ai-memory Four-Rung Auth Ladder?

The authentication system in ai-memory operates as a **sticky ladder** defined in [`docs/users.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/users.md)【/cache/repos/github.com/akitaonrails/ai-memory/main/docs/users.md#L55-L64】. When a request arrives, the server checks credentials in a specific order, stopping at the first applicable rung. This design prevents privilege escalation attacks where a request might attempt to climb from anonymous to root access.

The ladder supports both single-user deployments (using root tokens) and multi-user environments (using database-stored user tokens), with an optional proxy tier for SSO integration.

## The Four Authentication Rungs Explained

### Rung 0: Anonymous Access

**Trigger:** No `Authorization` header or `[auth].bearer_token` is supplied.

Requests without authentication proceed as **anonymous** and carry no identity. This preserves backward compatibility with pre-multi-user behavior, allowing unauthenticated reads while restricting writes based on server configuration.

### Rung 1: Root Authentication

**Trigger:** The bearer token matches `[auth].bearer_token` from the configuration.

Root requests receive **full administrative privileges**. If `[auth].root_username` is configured, audit logs attribute writes to that name; otherwise, the request remains technically anonymous in logs but retains root capabilities. All `/admin/*` endpoints require this authentication level when multi-user mode is active.

### Rung 1b: Proxy-Asserted User

**Trigger:** The bearer token matches the distinct `[auth].actor_proxy_bearer_token` and trusted `X-Memory-Actor-*` headers are present.

This intermediate tier allows a **trusted reverse proxy** to assert user identity via headers like `X-Memory-Actor-User`. According to the source documentation, the server treats these requests as standard user requests unless the OIDC issuer/subject pair exactly matches the configured root pair【/cache/repos/github.com/akitaonrails/ai-memory/main/docs/users.md#L78-L88】. Missing or malformed identity headers result in immediate rejection rather than fallback to anonymous access.

### Rung 2: DB User Authentication

**Trigger:** The bearer token does not match root or proxy tokens but validates against the `users.token_hash` table.

DB-user tokens occupy the standard multi-user tier. The server computes **SHA-256(token + ":" + `[auth].token_pepper`)** and compares it against stored hashes in [`crates/ai-memory-store/src/users.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/users.rs). Matching tokens grant normal read/write API access while restricting administrative endpoints. The audit log records the specific username, email, and name associated with the token.

### Rung 3: 401 Unauthorized

**Trigger:** A bearer token is present but matches none of the above tiers.

Unknown credentials result in **immediate rejection** (401 Unauthorized). This rung closes the security bypass—unlike systems that fall back to anonymous access when authentication fails, ai-memory treats unrecognized tokens as errors.

## How Authentication Is Resolved

The resolution logic implemented in [`crates/ai-memory-core/src/auth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/auth.rs) evaluates the `AuthLevel::resolve` method sequentially. The system is **strictly sticky**: once a request matches a rung, evaluation stops and never climbs to a higher tier【/cache/repos/github.com/akitaonrails/ai-memory/main/docs/users.md#L65-L68】.

Critical safety checks include:

- **Token distinctness**: The server refuses startup if `[auth].bearer_token` equals `[auth].actor_proxy_bearer_token`【/cache/repos/github.com/akitaonrails/ai-memory/main/docs/users.md#L91-L93】.
- **Proxy isolation**: The proxy token should only be used when ai-memory sits behind a trusted reverse proxy that sanitizes external `X-Memory-Actor-*` headers.

## Configuration and Implementation Details

### Configuration Schema

Define the ladder parameters in your [`config.toml`](https://github.com/akitaonrails/ai-memory/blob/main/config.toml):

```toml
[auth]
bearer_token = "<root-token>"               # Rung 1: Root admin token

actor_proxy_bearer_token = "<proxy-token>"  # Rung 1b: Optional SSO proxy token

token_pepper = "<generated-pepper>"         # Rung 2: Required for DB-user hashing

root_username = "admin"                     # Optional attribution for root actions

```

### Request Examples by Rung

```bash

# Rung 0: Anonymous request (no token)

curl http://localhost:49374/api/v1/pages/foo.md

# Rung 1: Root request (full admin access)

curl -H "Authorization: Bearer <root-token>" \
     http://localhost:49374/admin/users

# Rung 1b: Proxy-asserted user (SSO via trusted proxy)

curl -H "Authorization: Bearer <proxy-token>" \
     -H "X-Memory-Actor-User: alice" \
     -H "X-Memory-Actor-Email: alice@example.com" \
     http://localhost:49374/api/v1/pages/foo.md

# Rung 2: DB-user request (standard multi-user token)

curl -H "Authorization: Bearer <alice-db-token>" \
     http://localhost:49374/api/v1/pages/foo.md

```

### Source File References

| Component | File Path | Purpose |
|-----------|-----------|---------|
| **Auth Resolution** | [`crates/ai-memory-core/src/auth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/auth.rs) | Implements `AuthLevel::resolve` logic and `ActorContext` injection |
| **Token Storage** | [`crates/ai-memory-store/src/users.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/users.rs) | Stores SHA-256 hashed tokens for Rung 2 verification |
| **Proxy Middleware** | [`crates/ai-memory-mcp/src/middleware/auth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/middleware/auth.rs) | Processes `X-Memory-Actor-*` headers for Rung 1b |
| **Admin Guards** | [`crates/ai-memory-mcp/src/routes/admin.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/routes/admin.rs) | Enforces Root-only access to `/admin/*` endpoints |

## Security Considerations

When deploying the ai-memory four-rung auth ladder in production, consider these constraints:

- **Proxy token exposure**: Never expose `[auth].actor_proxy_bearer_token` directly to client applications. This credential grants the ability to impersonate any user via headers and should remain restricted to your internal reverse proxy infrastructure.
- **Pepper generation**: The `[auth].token_pepper` value must be cryptographically random and consistent across server restarts. Changing the pepper invalidates all existing DB-user tokens (Rung 2).
- **Admin isolation**: Normal DB users (Rung 2) cannot access administrative endpoints. Route guards in [`crates/ai-memory-mcp/src/routes/admin.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/routes/admin.rs) explicitly check for `AuthLevel::Root` regardless of other permissions.

## Summary

- The ai-memory four-rung auth ladder evaluates requests in strict order: Anonymous → Root → Proxy-Asserted User → DB User → 401 Rejection.
- **First-match wins**: Once a request authenticates at any rung, it never escalates to higher privileges.
- Root and proxy tokens must be distinct values; the server fails fast on configuration conflicts.
- DB-user tokens use SHA-256 hashing with a configurable pepper to prevent rainbow table attacks against the `users` table.
- The proxy tier (Rung 1b) enables SSO integration but requires a trusted network boundary to prevent header spoofing.

## Frequently Asked Questions

### What happens if I provide a wrong bearer token in ai-memory?

The request immediately receives a **401 Unauthorized** response. Unlike some systems that fall back to anonymous access, ai-memory treats unrecognized tokens as authentication failures and rejects the request at Rung 3 of the auth ladder.

### Can I use the same token for root and proxy authentication?

No. The server performs a startup validation that ensures `[auth].bearer_token` and `[auth].actor_proxy_bearer_token` are distinct values. If these collide, ai-memory refuses to start【/cache/repos/github.com/akitaonrails/ai-memory/main/docs/users.md#L91-L93】.

### How are DB-user tokens stored securely?

DB-user tokens are stored as **SHA-256 hashes** computed from the concatenation of the raw token, a colon separator, and the `[auth].token_pepper` value. The raw token is never persisted; only this peppered hash exists in the database, making offline brute-force attacks computationally expensive even if the `users` table is compromised.

### Why does the proxy tier exist in the auth ladder?

The proxy tier (Rung 1b) enables **SSO integration** and service mesh authentication. When ai-memory runs behind a trusted reverse proxy (like Envoy or NGINX with OAuth2/OIDC), the proxy handles the complex authentication flow and asserts the user identity via `X-Memory-Actor-*` headers. This separates authentication concerns from the core application while maintaining the security guarantees of the four-rung ladder.