# How pi-web Supports Dual-Auth Providers with OAuth and API Key Methods: A Complete Technical Guide

> Discover how pi-web supports dual-auth providers using OAuth and API keys. Learn about capability-based discovery and type-aware credential storage for seamless authentication.

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

---

**pi-web treats dual-auth providers as single logical entities that can authenticate via either OAuth or API key, using capability-based discovery and type-aware credential storage to keep both methods available without hard-coding provider IDs.**

The **agegr/pi-web** repository implements a flexible authentication system for AI model providers that support both OAuth and API key methods. Rather than duplicating providers or forcing a single authentication path, the codebase dynamically discovers capabilities from the Pi SDK and surfaces both options in the UI. This design ensures providers like Anthropic, GitHub Copilot, and Kimi-Coding automatically appear with the correct authentication badges without manual configuration.

## Capability Discovery from the Pi SDK

The foundation of dual-auth support lives in [`lib/provider-listing-runtime.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing-runtime.ts). This module queries `ModelRuntime` to extract each provider's authentication capabilities and any stored credentials.

```typescript
const credentialTypes = new Map<string, ProviderCredentialType>();
for (const cred of await modelRuntime.listCredentials()) {
  if (cred.type === "api_key" || cred.type === "oauth") {
    credentialTypes.set(cred.providerId, cred.type);
  }
}
// build ProviderListingInput for each provider …

```

This runtime detection means new providers added to the Pi SDK immediately surface their auth methods without code changes to pi-web. The `credentialTypes` map tracks which method is currently active for each provider, preventing conflicting credential states.

## Pure Functions for Provider List Building

The [`lib/provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts) module contains two core functions that transform raw SDK data into UI-ready lists:

**`buildApiKeyProviderList`** (lines 81-99) filters providers where `hasApiKeyLogin` is true and excludes those already authenticated via OAuth. It adds `supportsOAuth: true` for dual-auth providers so the UI can display "also supports OAuth" badges.

**`buildOAuthProviderList`** (lines 102-117) does the inverse: providers with `hasOAuth` get listed, with `supportsApiKey: true` added for reciprocal UI cues.

This separation allows the frontend to present distinct entry points while maintaining awareness of alternative authentication methods.

## API Routes for Separate Auth Flows

### API-Key Providers Endpoint

`GET /api/auth/all-providers` returns the complete list of API-key capable providers, including dual-auth providers that haven't been OAuth-authenticated yet.

```typescript
// app/api/auth/all-providers/route.ts
export async function GET() {
  const providers = await buildApiKeyProviderList(runtimeInput);
  return Response.json({ providers });
}

```

### OAuth Login Endpoint

`GET /api/auth/login/[provider]` implements a Server-Sent Events (SSE) flow for OAuth authentication (lines 44-70). The same route handles `POST` requests for user responses during multi-step OAuth flows.

## Credential Storage with Type Safety

The [`lib/provider-credential-store.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-credential-store.ts) module persists credentials to `~/.pi/agent/auth.json` with strict type preservation. Two critical functions manage this:

- **`storeProviderCredential`** writes credentials tagged with their type (`api_key` or `oauth`)
- **`removeStoredCredentialIfType`** validates type matching before deletion

This prevents accidental credential corruption. If an API-key credential exists, OAuth deletion attempts receive a `type_mismatch` error, and vice versa.

## Complete Authentication Flows

### API-Key Authentication

The `POST /api/auth/api-key/[provider]` endpoint validates provider capability before storing:

```typescript
// Example: Store an API key for Anthropic
await fetch('/api/auth/api-key/anthropic', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ apiKey: 'sk-ant-…' }),
});

```

The route handler in `app/api/auth/api-key/[provider]/route.ts` (lines 20-55) verifies `hasApiKeyLogin` before calling `storeProviderCredential`.

### OAuth Authentication

OAuth flows use SSE for real-time status updates:

```typescript
const es = new EventSource(`/api/auth/login/github-copilot`);

es.addEventListener('message', ev => {
  const data = JSON.parse(ev.data);
  
  switch (data.type) {
    case 'auth':
      window.open(data.url, '_blank');
      break;
    case 'device_code':
      console.log(`Enter ${data.userCode} at ${data.verificationUri}`);
      break;
    case 'prompt_request':
    case 'select_request':
      // Forward to UI, then POST response
      break;
    case 'success':
      es.close();
      break;
  }
});

```

The server sends `select_request`, `prompt_request`, `auth`, and `device_code` events, receiving answers via companion POST requests to the same route.

## UI Integration with Automatic Badging

Components like [`SkillsConfig.tsx`](https://github.com/agegr/pi-web/blob/main/SkillsConfig.tsx) and [`ModelsConfig.tsx`](https://github.com/agegr/pi-web/blob/main/ModelsConfig.tsx) consume both provider lists and render authentication method badges dynamically. Because capabilities derive from SDK flags rather than hard-coded lists, new dual-auth providers automatically display "Supports OAuth" or "Supports API key" indicators without frontend changes.

## Summary

- **Dynamic discovery** via [`provider-listing-runtime.ts`](https://github.com/agegr/pi-web/blob/main/provider-listing-runtime.ts) eliminates hard-coded provider IDs
- **Capability-based filtering** in [`provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/provider-listing.ts) creates clean separation between auth methods while preserving dual-auth awareness
- **Dual endpoints** (`/api/auth/all-providers` and `/api/auth/login/[provider]`) serve distinct UI entry points
- **Type-safe storage** in [`provider-credential-store.ts`](https://github.com/agegr/pi-web/blob/main/provider-credential-store.ts) prevents credential conflicts
- **Automatic UI updates** ensure new providers appear with correct badges immediately

## Frequently Asked Questions

### How does pi-web prevent mixing OAuth and API key credentials for the same provider?

The `removeStoredCredentialIfType` function in [`lib/provider-credential-store.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-credential-store.ts) validates the stored credential type before deletion. When deleting an API-key credential, it checks that the stored type matches; if OAuth is stored instead, it returns a `type_mismatch` error. This enforcement prevents accidental credential overwrites.

### What happens when a new dual-auth provider is added to the Pi SDK?

No code changes are required in pi-web. The [`provider-listing-runtime.ts`](https://github.com/agegr/pi-web/blob/main/provider-listing-runtime.ts) module queries `ModelRuntime.getProviders()` and reads each provider's `auth` capabilities dynamically. The provider automatically appears in both filtered lists with appropriate `supportsOAuth` or `supportsApiKey` flags set based on its capability metadata.

### Can users switch from API key to OAuth authentication for the same provider?

Yes, but not directly. Users must first delete their existing API-key credential via `DELETE /api/auth/api-key/[provider]`, then initiate OAuth flow through `GET /api/auth/login/[provider]`. The type-checking in the credential store prevents mixed states during this transition.

### Where are provider credentials physically stored?

Credentials persist in `~/.pi/agent/auth.json` as managed by [`lib/provider-credential-store.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-credential-store.ts). The file maintains a record of provider IDs mapped to their credential type and value, enabling the runtime to reconstruct authentication state on application startup.