# How OIDC Device Authentication Flow Works for Native Hook Auth in ai-memory

> Discover how the ai-memory CLI leverages OIDC device authentication flow for native hook auth, enabling browserless login via a separate device. Learn more.

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

---

**The ai-memory CLI uses the OAuth 2.0 Device Authorization Grant to authenticate native hook commands without requiring a browser on the client machine, polling the token endpoint until the user completes login on a separate device.**

The `akitaonrails/ai-memory` project enables secure, password-less authentication for developer lifecycle hooks by implementing the **OIDC device flow**. This approach lets command-line tools obtain access tokens by delegating user authentication to a browser on any device, making it ideal for headless environments and native hook invocations.

## What Is the OIDC Device Flow?

The **Device Authorization Grant** (RFC 8628) solves a fundamental CLI problem: how to authenticate a user when the application cannot launch or control a browser. Instead of redirecting within the same process, the flow separates code execution from user interaction.

In `ai-memory`, this flow authenticates **native hook commands**—lifecycle events like `session-start` or `project-init` that trigger when developers interact with AI-assisted tooling.

## Step-by-Step Flow Implementation

### 1. Initiate Device Authorization Request

When a developer runs `ai-memory auth login oidc-device`, the CLI constructs a `POST` request to the provider's **device-authorization endpoint**. This is 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 1-30).

The response contains four critical fields:

- **`device_code`** – opaque string for token polling
- **`user_code`** – short, human-readable code for manual entry
- **`verification_uri`** or **`verification_uri_complete`** – URL where the user authenticates
- **`poll_interval`** – seconds the CLI must wait between polling attempts

### 2. Display Verification Instructions to the User

The CLI prints instructions directly to stdout. From the source in **[`oidc.rs`](https://github.com/akitaonrails/ai-memory/blob/main/oidc.rs)**:

```text
Visit https://issuer.example.com/realms/team/device and enter code: ABCD-EFGH

```

The user can complete this step on any device—phone, tablet, or workstation—with no requirement for browser access from the CLI process itself.

### 3. User Completes Authentication in Browser

This step occurs outside the ai-memory codebase. The user visits the verification URL, signs in with their OIDC provider, and authorizes the "ai-memory-cli" client. The provider then binds the `device_code` to a pending token grant.

### 4. CLI Polls the Token Endpoint

While awaiting user approval, the CLI enters a polling loop 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 66-100). It repeatedly `POST`s to the **token endpoint** with:

```http
grant_type=urn:ietf:params:oauth:grant-type:device_code
&device_code=xxxxxxxx
&client_id=ai-memory-cli

```

The polling loop handles three server responses:

- **`authorization_pending`** – user has not yet approved; sleep for `poll_interval` and retry
- **`slow_down`** – increase polling interval to respect rate limits
- **Success** – receive `access_token` and optional `refresh_token`

### 5. Persist Token to Local Storage

Upon successful authentication, the CLI writes token metadata to **`$DATA_DIR/auth.json`** (default: `$HOME/.local/share/ai-memory/`). The storage helpers—`store_token`, `load_token`, `remove_token`—are colocated in **[`oidc.rs`](https://github.com/akitaonrails/ai-memory/blob/main/oidc.rs)**.

Stored fields include:

```json
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_at": "2027-08-20T12:34:56Z",
  "issuer": "https://issuer.example.com/realms/team",
  "client_id": "ai-memory-cli"
}

```

### 6. Hook Commands Automatically Attach the Token

When executing **`ai-memory hook post`** or similar commands, the hook runner reads the stored token and injects it into the `Authorization` header. As noted in **[`crates/ai-memory-hooks/src/router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/router.rs)** (lines 543-560), the system recognizes OIDC-authenticated requests and derives an identity key from the token claims.

```bash
ai-memory hook post \
  --event session-start \
  --payload '{"session_id":"1234","project":"myproj"}'

```

This automatically produces:

```http
Authorization: Bearer eyJhbGciOi...

```

### 7. Server Validates and Distinguishes Token Types

The server layer in **[`crates/ai-memory-mcp/src/auth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/auth.rs)** (lines 143-156) implements a dual-token strategy:

- **OIDC-derived tokens** – authenticate per-developer hook writes
- **Static root bearer token** (`AI_MEMORY_AUTH_TOKEN`) – required for privileged admin routes

This separation ensures that compromise of a developer OIDC token cannot escalate to server administration.

## Fallback Behavior and Precedence

According to **[`docs/install.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/install.md)** (lines 986-1002), the authentication system observes strict precedence:

1. If `AI_MEMORY_AUTH_TOKEN` environment variable is set, use it (static root token)
2. Otherwise, load and use the OIDC token from [`auth.json`](https://github.com/akitaonrails/ai-memory/blob/main/auth.json)

This enables operation behind OIDC-aware reverse proxies while maintaining backward compatibility for automated deployments.

## Practical Usage Examples

### Complete Device Flow Login

```bash
ai-memory auth login oidc-device \
  --issuer "https://issuer.example.com/realms/team" \
  --client-id "ai-memory-cli"

```

Wait for browser approval, then verify stored credentials:

```bash
ai-memory auth status

```

### Trigger Hook with Automatic Authentication

```bash

# No explicit token needed—CLI reads from auth.json

ai-memory hook post --event project-init --payload '{"lang":"rust"}'

```

### Thin-Client Commands Reuse the Same Token

Even non-hook commands like `ai-memory status` or `ai-memory search` automatically pick up the OIDC token when no static bearer is configured, providing unified authentication across all CLI surfaces.

## Key Source Files Reference

| File | Responsibility |
|------|----------------|
| **[`crates/ai-memory-llm/src/oidc.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-llm/src/oidc.rs)** | Full device flow implementation: authorization request, polling loop, token persistence, refresh handling |
| **[`crates/ai-memory-hooks/src/router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/router.rs)** (lines 543-560) | OIDC request recognition and identity key derivation for incoming hooks |
| **[`crates/ai-memory-mcp/src/auth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/auth.rs)** (lines 143-156) | Server-side token validation and distinction between OIDC and static root tokens |
| **[`docs/install.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/install.md)** (lines 364-379, 986-1002) | CLI usage instructions and fallback behavior documentation |
| **[`docs/users.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/users.md)** (lines 61-127) | Identity model documentation covering `issuer`, `subject`, and OIDC pair integration |

## Summary

- The **OIDC device flow** eliminates browser dependencies from the CLI authentication path by separating code generation from user approval
- **[`crates/ai-memory-llm/src/oidc.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-llm/src/oidc.rs)** implements the complete RFC 8628 flow including polling, error handling, and token storage
- Native hook commands automatically reuse stored OIDC tokens via the `Authorization: Bearer` header without manual intervention
- The server maintains **defense-in-depth** by requiring static root tokens for admin routes while accepting OIDC tokens for per-developer hook operations
- Environment variable `AI_MEMORY_AUTH_TOKEN` takes precedence, enabling hybrid deployment scenarios

## Frequently Asked Questions

### What happens if the user never completes browser authentication?

The CLI continues polling until the `device_code` expires (typically 15 minutes, per provider policy). The polling loop in **[`oidc.rs`](https://github.com/akitaonrails/ai-memory/blob/main/oidc.rs)** handles `authorization_pending` indefinitely within that window. After expiration, the CLI exits with an error instructing the user to restart `ai-memory auth login oidc-device`.

### Can the CLI automatically refresh expired OIDC tokens?

Yes, if the token response included a `refresh_token`, the `store_token` and `load_token` helpers in **[`oidc.rs`](https://github.com/akitaonrails/ai-memory/blob/main/oidc.rs)** persist it alongside the access token. On detection of expiry, the client attempts silent refresh before prompting for re-authentication. If no refresh token exists, the user must repeat the device flow.

### Why does ai-memory use device flow instead of the authorization code flow with PKCE?

The **authorization code flow** requires either localhost redirect handling (fragile with port conflicts and firewalls) or out-of-band URI schemes (OS-dependent registration). The **device flow** works reliably in containers, SSH sessions, and CI environments where no browser is available. As documented in **[`docs/install.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/install.md)** (lines 364-379), this choice prioritizes portability over the marginally faster single-redirect UX of PKCE.

### How does the server distinguish between a valid OIDC token and the static root token?

Per **[`crates/ai-memory-mcp/src/auth.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/auth.rs)** (lines 143-156), the server examines token structure and validation path. OIDC tokens validate against the configured issuer's JWKS endpoint, while the static root token matches a constant secret. Route handlers declare required token types: hook endpoints accept either, admin endpoints reject OIDC tokens regardless of scope claims.