# How to Set Up Multi-User Authentication with OIDC Device Tokens in ai-memory

> Set up multi-user authentication in ai-memory using OIDC device tokens. Securely isolate user data with a simple login command for enhanced privacy and access control.

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

---

**To enable multi-user authentication in ai-memory, configure `root_issuer` and `root_subject` in [`config.toml`](https://github.com/akitaonrails/ai-memory/blob/main/config.toml), then run `ai-memory auth login oidc-device` to generate a device token that automatically isolates each user's data in separate operator namespaces.**

The akitaonrails/ai-memory repository implements a true multi-user model using **OIDC device-authorization tokens** stored in a shared [`auth.json`](https://github.com/akitaonrails/ai-memory/blob/main/auth.json) file. Each user's OIDC pair (`issuer`, `subject`) generates a deterministic namespace, ensuring complete isolation between operators while allowing headless CI authentication through the device flow.

## Configure the Root OIDC Identity

Start by defining the root operator in your [`config.toml`](https://github.com/akitaonrails/ai-memory/blob/main/config.toml). This replaces the legacy single-user bearer token model with a cryptographically verified OIDC identity.

Add the following under the `[auth]` section:

```toml
[auth]
root_issuer = "https://login.example.com/realms/ai-memory"
root_subject = "admin@example.com"
root_username = "alice"
root_email    = "alice@example.com"
root_name     = "Alice Admin"

```

The `root_issuer` and `root_subject` pair must match the OIDC token used for administrative operations. According to the source code in [`crates/ai-memory-cli/src/config.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/config.rs) (lines 62–68), these fields define which identity receives **root capability** and can perform privileged actions like `ai-memory admin purge-project`.

## Authenticate Users via Device Flow

The device flow allows headless authentication—ideal for CI runners and containerized environments.

Run the login command:

```bash
$ ai-memory auth login oidc-device
> Open https://login.example.com/realms/ai-memory/device in a browser
> Enter code: ABCD-EFGH

```

The CLI executes the full device-authorization grant as implemented in [`crates/ai-memory-llm/src/oidc.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-llm/src/oidc.rs) (lines 74–85):

1. Discovers the OIDC provider endpoints
2. Requests a device code via `request_device_code`
3. Polls the token endpoint until authorization completes
4. Stores the token via `OidcToken::save` (lines 92–98)

The token persists to `$XDG_CONFIG_HOME/ai-memory/auth.json` under the `oidc` key, preserving existing entries for other providers like `openai` or `copilot`.

## Automatic Token Management

Once stored, the CLI automatically loads the OIDC token for every subcommand unless overridden with `--auth-token`.

The lookup logic in [`crates/ai-memory-cli/src/commands/hook_spool.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/commands/hook_spool.rs) follows this priority:

```rust
let token = if let Some(t) = cli_opt.auth_token {
    Some(t)
} else {
    OidcToken::load(&auth_path)?.map(|t| t.access.expose_secret().to_string())
};

```

**Token refresh** happens automatically when `token.needs_refresh()` returns true. The CLI calls `refresh_access_token` (defined in [`crates/ai-memory-llm/src/oidc.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-llm/src/oidc.rs), lines 64–84) to obtain a fresh access token using the stored refresh grant, then updates [`auth.json`](https://github.com/akitaonrails/ai-memory/blob/main/auth.json) via `OidcToken::save` without user intervention.

## Server-Side Validation and Namespacing

Incoming HTTP requests to the ai-memory server must include the JWT in the `Authorization: Bearer` header.

The server validates tokens in [`crates/ai-memory-mcp/src/auth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/auth.rs):

```rust
let jwt = extract_bearer(jwt_header)?;
let claims = validate_oidc(&jwt, config.oidc_jwks_url)?;
let actor = ActorContext {
    user: Some(User {
        oidc_issuer: claims.iss.clone(),
        oidc_subject: claims.sub.clone(),
    }),
};

```

After validation, the server constructs the **operator namespace** using the logic in [`crates/ai-memory-core/src/actor.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/actor.rs) (lines 320–340). The namespace format is:

```

o-<len(issuer)>-<base64(issuer)>-<subject>

```

This identifier isolates all slots, handoffs, and wiki pages to the specific `(issuer, subject)` pair, guaranteeing that users cannot access each other's data.

## Root-Only Operations

Only the OIDC identity matching the configured `root_issuer` and `root_subject` receives `AuthLevel::Root`. All other authenticated users operate within their individual namespaces with standard privileges.

To perform administrative tasks, ensure your active OIDC token (loaded from [`auth.json`](https://github.com/akitaonrails/ai-memory/blob/main/auth.json) or passed via `--auth-token`) corresponds to the root pair defined in step one. The server validates this match in the `AuthSettings` logic referenced in [`crates/ai-memory-cli/src/config.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/config.rs).

## Summary

- **Configure root identity**: Set `root_issuer` and `root_subject` in [`config.toml`](https://github.com/akitaonrails/ai-memory/blob/main/config.toml) to establish administrative privileges.
- **Device flow login**: Run `ai-memory auth login oidc-device` to authenticate without browser integrations.
- **Automatic persistence**: Tokens store securely in [`auth.json`](https://github.com/akitaonrails/ai-memory/blob/main/auth.json) and load automatically via `OidcToken::load`.
- **Per-user isolation**: The server maps each `(issuer, subject)` pair to a unique operator namespace using deterministic hashing in `ai_memory_core::actor`.
- **Seamless refresh**: Expired tokens update automatically using `refresh_access_token` before API requests.

## Frequently Asked Questions

### How does ai-memory store multiple user tokens without conflicts?

Each user runs `ai-memory auth login oidc-device` independently, which calls `OidcToken::save` to write the token to [`auth.json`](https://github.com/akitaonrails/ai-memory/blob/main/auth.json). Because the CLI loads the token from this shared file on every invocation via `OidcToken::load`, different users simply maintain separate [`auth.json`](https://github.com/akitaonrails/ai-memory/blob/main/auth.json) files in their respective `$XDG_CONFIG_HOME` directories, or the system uses distinct `--auth-dir` paths for service accounts.

### Can I use OIDC device tokens in CI/CD pipelines?

Yes. The device flow is designed for headless environments. Run `ai-memory auth login oidc-device` once interactively to generate the initial token, commit the resulting [`auth.json`](https://github.com/akitaonrails/ai-memory/blob/main/auth.json) (or mount it as a secret), and the CLI will automatically handle token refresh during subsequent CI runs. This eliminates the need to embed long-lived bearer tokens in environment variables.

### What happens if my OIDC token expires during a long-running operation?

The CLI checks token expiry before each request using `token.needs_refresh()`. If the token nears expiration, the `refresh_access_token` function (in [`crates/ai-memory-llm/src/oidc.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-llm/src/oidc.rs)) executes a background refresh, saves the new credentials via `OidcToken::save`, and continues the operation without interruption. No manual intervention is required.

### How do I revoke or invalidate a user's access?

Delete the [`auth.json`](https://github.com/akitaonrails/ai-memory/blob/main/auth.json) file from the user's configuration directory (or remove the specific `oidc` key from it). On the server side, the validation logic in [`crates/ai-memory-mcp/src/auth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/auth.rs) always checks the OIDC provider's JWKS endpoint, so revoking the token at the identity provider level will immediately invalidate it for future requests, regardless of the local [`auth.json`](https://github.com/akitaonrails/ai-memory/blob/main/auth.json) state.