# How Pi Web Handles Authentication for Different Model Providers

> Discover how Pi Web manages authentication for providers like Anthropic and OpenAI using OAuth flows or API keys. Learn more about its robust security for model integrations.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: how-to-guide
- Published: 2026-07-30

---

**Pi Web delegates all provider authentication to the `ModelRuntime` class from `@earendil-works/pi-coding-agent`, supporting interactive OAuth flows via Server-Sent Events for providers like Anthropic and OpenAI, while using simple HTTP POST requests for static API-key credentials.**

The `agegr/pi-web` repository implements a flexible, provider-agnostic authentication layer that abstracts away the differences between AI model providers. By centralizing authentication logic in the `ModelRuntime` class, the application handles everything from interactive OAuth logins to static API-key storage through a uniform interface used by Next.js route handlers.

## The ModelRuntime Authentication Hub

At the core of Pi Web's authentication system is the **`ModelRuntime`** class provided by the external package `@earendil-works/pi-coding-agent`. This runtime maintains a registry of each provider's capabilities, distinguishing between **`oauth`** and **`api_key`** authentication methods.

When a route handler needs to authenticate a user, it instantiates a `ModelRuntime` and calls the appropriate methods based on the provider type. The runtime exposes **`getProviders()`** to enumerate available providers and **`login()`** to initiate the authentication flow, using a callback-based `prompt` system to communicate with the client UI.

## OAuth Authentication via Server-Sent Events

For OAuth-based providers such as **Anthropic**, **GitHub Copilot**, and **OpenAI Codex**, Pi Web implements an interactive authentication flow using **Server-Sent Events (SSE)**. This approach drives a dynamic login UI that can request user input at multiple steps.

### SSE Implementation and Temporary Tokens

The OAuth flow is handled in `app/api/auth/login/[provider]/route.ts`. When a client opens an SSE connection to `GET /api/auth/login/[provider]`, the server:

1. Creates a `ModelRuntime` instance and verifies the provider supports OAuth
2. Generates a temporary token in the format `<provider>-<ts>-<rand>` to track the request/response pair
3. Invokes `modelRuntime.login(provider, "oauth", ...)` with a `prompt` callback that streams UI instructions to the client

The SSE stream sends event types including `select_request`, `prompt_request`, `auth` (containing URLs to open), `device_code`, `success`, and `error`. When the client receives a prompt, it submits the response via `POST /api/auth/login/[provider]` with the temporary token, which resolves the promise waiting in the runtime.

```typescript
// Client-side OAuth initiation
const evtSource = new EventSource(`/api/auth/login/${provider}`);

evtSource.addEventListener('message', async e => {
  const data = JSON.parse(e.data);
  switch (data.type) {
    case 'select_request':
    case 'prompt_request': {
      const answer = await showPromptToUser(data);
      await fetch(`/api/auth/login/${provider}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ token: data.token, code: answer })
      });
      break;
    }
    case 'auth':
      window.open(data.url, '_blank');
      break;
    case 'success':
      console.log('Login succeeded');
      evtSource.close();
      break;
    case 'error':
      console.error('Login error:', data.message);
      evtSource.close();
      break;
  }
});

```

After successful OAuth authentication, the route calls **`invalidateModelsCache()`** to refresh the available model list.

## API-Key Authentication

For providers that accept static credentials, Pi Web uses a simpler HTTP-based flow handled in `app/api/auth/api-key/[provider]/route.ts`. The supported operations include:

- **`GET`** to check authentication status
- **`POST`** to store a new API key
- **`DELETE`** to remove stored credentials

When storing a key via `POST`, the route creates a `ModelRuntime` and calls `modelRuntime.login(provider, "api_key", ...)`. The `prompt` callback first receives a `select` prompt to choose the authentication method (e.g., "api-key" versus "bearer-token"), followed by a `secret` prompt where the raw key is supplied.

```typescript
// Store an API key
await fetch(`/api/auth/api-key/${provider}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ apiKey: 'sk-my-secret-key' })
});

```

After successful storage, the models cache is invalidated to reflect the new authentication state.

## Filtering Providers by Authentication Requirements

Not all providers require UI-based authentication. The route [`app/api/auth/all-providers/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/auth/all-providers/route.ts) (lines 5-30) filters the complete provider list to return only those requiring stored credentials.

This handler enumerates providers from `ModelRuntime.getProviders()` and excludes:

- Providers missing `auth.apiKey?.login` capabilities
- Providers listed in the hard-coded **`OAUTH_PROVIDER_IDS`** set
- Providers with `auth.status.source` equal to **`"models_json_key"`** (custom providers configured via [`models.json`](https://github.com/agegr/pi-web/blob/main/models.json))

The filtered response includes fields for `id`, `displayName`, `configured`, `source`, and `modelCount`, driving the Settings UI with only relevant authentication options.

```typescript
// Fetch providers requiring authentication
const resp = await fetch('/api/auth/all-providers');
const { providers } = await resp.json();
/* Example output:
[
  { id: 'openai', displayName: 'OpenAI', configured: true, source: 'env', modelCount: 12 },
  { id: 'anthropic', displayName: 'Anthropic', configured: false, source: undefined, modelCount: 5 }
]
*/

```

## Clearing Credentials and Logout

OAuth credentials are cleared via `app/api/auth/logout/[provider]/route.ts` (lines 10-17), which creates a `ModelRuntime` and calls the logout method for the specified provider. API-key credentials are removed via `DELETE` requests to the API-key endpoint.

```typescript
// Logout from OAuth provider
await fetch(`/api/auth/logout/${provider}`, { method: 'POST' });

// Remove API key
await fetch(`/api/auth/api-key/${provider}`, { method: 'DELETE' });

```

## Summary

- **Pi Web** uses the **`ModelRuntime`** class from `@earendil-works/pi-coding-agent` as a centralized authentication hub, abstracting provider-specific implementations behind a uniform interface.
- **OAuth providers** use interactive **Server-Sent Events** with temporary tokens to drive multi-step login UIs, handled in `app/api/auth/login/[provider]/route.ts`.
- **API-key providers** use simple HTTP POST/DELETE requests managed by `app/api/auth/api-key/[provider]/route.ts`, supporting selection prompts for different credential types.
- The [`app/api/auth/all-providers/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/auth/all-providers/route.ts) endpoint filters out unauthenticated and custom providers (those configured via [`models.json`](https://github.com/agegr/pi-web/blob/main/models.json)), exposing only providers requiring credential storage.
- All authentication changes trigger **`invalidateModelsCache()`** to ensure the model list stays synchronized with the current authentication state.

## Frequently Asked Questions

### How does Pi Web initiate an OAuth login flow?

Pi Web initiates OAuth by opening an SSE connection to `GET /api/auth/login/[provider]`, which creates a `ModelRuntime` instance and streams interactive prompts to the client. The client submits responses via `POST` requests to the same endpoint using temporary tokens to correlate the request/response pairs.

### What distinguishes API-key authentication from OAuth in Pi Web's implementation?

API-key authentication uses simple HTTP POST requests to `app/api/auth/api-key/[provider]/route.ts` with the key in the request body, while OAuth uses streaming Server-Sent Events via `app/api/auth/login/[provider]/route.ts` to handle interactive, multi-step login flows that may require opening external URLs or entering device codes.

### Why do some providers not appear in the Pi Web authentication UI?

Providers configured via [`models.json`](https://github.com/agegr/pi-web/blob/main/models.json) (source **`models_json_key`**) or those that do not support stored credentials are filtered out by [`app/api/auth/all-providers/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/auth/all-providers/route.ts). This endpoint intentionally skips OAuth-only providers and custom configurations to show only providers requiring explicit credential storage through the UI.

### How does Pi Web handle cache invalidation after authentication?

After any successful login or logout operation, the route handlers call **`invalidateModelsCache()`** from [`lib/models-cache.ts`](https://github.com/agegr/pi-web/blob/main/lib/models-cache.ts) to clear the cached model list, ensuring subsequent requests reflect the updated authentication state and newly available or restricted models.