How pi-web Supports Dual-Auth Providers with OAuth and API Key Methods: A Complete Technical Guide
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. This module queries ModelRuntime to extract each provider's authentication capabilities and any stored credentials.
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 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.
// 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 module persists credentials to ~/.pi/agent/auth.json with strict type preservation. Two critical functions manage this:
storeProviderCredentialwrites credentials tagged with their type (api_keyoroauth)removeStoredCredentialIfTypevalidates 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:
// 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:
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 and 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.tseliminates hard-coded provider IDs - Capability-based filtering in
provider-listing.tscreates clean separation between auth methods while preserving dual-auth awareness - Dual endpoints (
/api/auth/all-providersand/api/auth/login/[provider]) serve distinct UI entry points - Type-safe storage in
provider-credential-store.tsprevents 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 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 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. 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →