# How the Pi-Web Auth API Handles OAuth Flows and API Key Storage

> Learn how the pi web auth API manages OAuth device-code flows and stores API keys securely in a centralized JSON file, preventing race conditions with file locking.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: deep-dive
- Published: 2026-07-31

---

**The pi-web auth API manages OAuth device-code flows and API key credentials through a centralized JSON store at `~/.pi/agent/auth.json`, using type-aware helpers with file locking to prevent race conditions.**

The `agegr/pi-web` repository implements a unified authentication layer that supports both OAuth device-code flows and static API key storage. This system persists credentials to a single JSON file with restrictive permissions while exposing REST endpoints for provider discovery, login, and secure logout.

## Credential Storage Architecture

All authentication data lives in **(`~/.pi/agent/auth.json`)**, a JSON file created with **mode `0o600`** (read/write for owner only). Each entry maps a provider ID to a credential object containing a `type` field—either `"oauth"` or `"api_key"`—followed by type-specific data such as `deviceCode`, `accessToken`, or `key`. This unified store allows the system to handle multiple authentication methods through a single, type-safe interface.

## OAuth Device-Code Flow Implementation

The OAuth implementation uses **Server-Sent Events (SSE)** to stream progress updates during the device-code authorization flow.

**Route:** `app/api/auth/login/[provider]/route.ts`

When a client opens an SSE connection to `GET /api/auth/login/[provider]`, the handler invokes the Pi SDK's `loginOAuthProvider()` method. This initiates the device-code flow and emits events (`code`, `verification_uri`, `access_token`) that the route forwards to the client. Upon successful completion, the SDK writes an OAuth credential object to [`auth.json`](https://github.com/agegr/pi-web/blob/main/auth.json):

```json
{
  "type": "oauth",
  "deviceCode": "...",
  "accessToken": "..."
}

```

**Client-side implementation:**

```javascript
const evtSource = new EventSource(`/api/auth/login/${providerId}`);
evtSource.onmessage = e => {
  const data = JSON.parse(e.data);
  if (data.type === 'device_code') {
    console.log('Enter this code:', data.code);
    console.log('Visit:', data.verification_uri);
  } else if (data.type === 'access_token') {
    console.log('Login successful!');
    evtSource.close();
  }
};

```

## API Key Storage and Validation

For providers that do not support OAuth, pi-web accepts static API keys via `POST /api/auth/api-key/[provider]`.

**Route:** `app/api/auth/api-key/[provider]/route.ts`

The handler validates that the provider is **not** an OAuth-only provider (returning HTTP 409 if it is), then writes a credential object to the store:

```json
{
  "type": "api_key",
  "key": "sk-XXXXXXXXXXXXXXXX"
}

```

**Example request:**

```javascript
await fetch(`/api/auth/api-key/${providerId}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ apiKey: 'sk-XXXXXXXXXXXXXXXX' })
});

```

## Secure Credential Removal

The logout endpoint `POST /api/auth/logout/[provider]` handles deletion for both credential types through the `removeStoredCredentialIfType()` helper in [`lib/provider-credential-store.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-credential-store.ts).

This utility performs the following steps:

1. Acquires a file lock using `proper-lockfile` to prevent concurrent modifications.
2. Verifies the stored credential's `type` matches the expected type (`"oauth"` or `"api_key"`).
3. Deletes the entry and rewrites [`auth.json`](https://github.com/agegr/pi-web/blob/main/auth.json) with mode `0o600`.

**Example logout:**

```javascript
await fetch(`/api/auth/logout/${providerId}`, { method: 'POST' });

```

## Provider Discovery

The frontend determines which authentication flow to present using `GET /api/auth/providers`.

**Implementation:** [`lib/provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts)

The `buildOAuthProviderList()` function queries the Pi SDK's model registry and filters for providers exposing an OAuth login method, returning an array of supported providers.

**Example usage:**

```javascript
const providers = await fetch('/api/auth/providers')
  .then(r => r.json()); 
// → [{ id: "github", name: "GitHub" }, …]

```

## Security Mechanisms

The authentication system implements several safeguards to protect credential data:

- **File permissions:** All writes to [`auth.json`](https://github.com/agegr/pi-web/blob/main/auth.json) use mode `0o600`, ensuring only the owner can read or write credentials.
- **Race condition prevention:** The `proper-lockfile` library locks the credential file during all read and write operations.
- **Type verification:** The logout route explicitly checks the stored credential type before deletion, preventing accidental removal of OAuth tokens when an API key is expected, or vice versa.

## Summary

- The pi-web auth API uses a single JSON file (`~/.pi/agent/auth.json`) to store both OAuth and API key credentials, differentiated by a `type` field.
- OAuth authentication follows the device-code flow via SSE endpoints that stream progress events to the client.
- API keys are validated against provider capabilities before storage to prevent conflicts with OAuth-only providers.
- The `removeStoredCredentialIfType()` helper in [`lib/provider-credential-store.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-credential-store.ts) ensures type-safe, locked deletion of credentials.
- File permissions (mode `600`) and `proper-lockfile` locking prevent unauthorized access and corruption.

## Frequently Asked Questions

### Where does pi-web store authentication credentials?

All credentials are stored in `~/.pi/agent/auth.json` as a single JSON object. The file is created with restrictive permissions (mode `0o600`) and contains entries for each provider with fields indicating whether the credential is an OAuth token or an API key.

### How does the OAuth device-code flow work in pi-web?

The flow initiates via `GET /api/auth/login/[provider]`, which opens an SSE connection. The server calls `pi.loginOAuthProvider()` to generate a device code, then streams events containing the verification URI and user code to the client. Once the user authorizes the device, the SDK writes the access token to [`auth.json`](https://github.com/agegr/pi-web/blob/main/auth.json) and sends a completion event to close the stream.

### Can I store an API key for a provider that also supports OAuth?

No. The API key endpoint (`POST /api/auth/api-key/[provider]`) explicitly checks if the provider supports OAuth and returns HTTP 409 Conflict if it does. This prevents credential conflicts and ensures providers use their intended authentication method according to the Pi SDK's provider registry.

### How does pi-web prevent race conditions when updating credentials?

All credential mutations use the `proper-lockfile` library to acquire exclusive file locks on [`auth.json`](https://github.com/agegr/pi-web/blob/main/auth.json) before reading or writing. Additionally, the `removeStoredCredentialIfType()` function verifies the credential type matches expectations before deletion, preventing accidental overwrites during concurrent operations.