Proposal: Provider Auth v2 (encrypted credential vault + multi-account OAuth + same-request rotation)
Context
I'm the author of the recently opened auth-v2 PRs from a new GitHub account (my prior account lost access). One of the PRs was closed as potential spam — totally fair given the account/volume — so I'm starting an issue to discuss direction first.
The full RFC is in specs/provider-auth-v2.md (included in the PR branches).
Problem
Today's auth path is effectively single-credential and makes it hard to support:
- multiple OAuth subscription accounts per provider
- deterministic rotation/cooldowns on
429within the same user request - refresh-on-expired (
401/403) where supported - a single composable integration point across providers
Proposal (Auth v2)
- Encrypted-at-rest credential vault (AES-256-GCM) with atomic writes + lockfile coordination.
- Multi-record credential store (providerId + namespace + label), enabling multiple accounts per provider.
- Provider auth registry/adapters to unify OAuth flows + apply auth headers for Anthropic/OpenAI/Google/Copilot/Qwen/Cursor.
- Fetch-level rotation middleware:
- rotate on
429(Retry-After-aware) and retry within the same request - refresh on
401/403when supported - persist pool ordering + cooldowns
- rotate on
- Migrate legacy auth files into the new store; keep behavior compatible.
Optional UX/docs pieces:
- TUI connected-accounts management + rotation stats
- Opt-in OpenAI-compatible model discovery via
/modelswith caching - Vault key management commands (init/export/import)
PRs (if you'd like to review code)
- #5742 auth v2 core (currently closed)
- #5743 TUI credential manager + rotation stats
- #5744 opt-in model discovery
- #5745 vault key CLI
- #5746 combined PR
Questions
- Is this direction acceptable for OpenCode?
- Would you prefer the work reviewed as split PRs or as a single PR?
- Any preferences on vault key location / migration strategy / naming before I iterate further?
Totally understand if this isn't a direction you want in core — consider this a proposal with a concrete implementation you can cherry-pick from (or close entirely).
Why I think it's valuable:
- Security: credentials are encrypted at rest (vault + atomic writes/locking) instead of living as plaintext JSON.
-
Reliability: same-request rotation on
429(Retry-After-aware) and refresh-on-expired where supported reduces user-visible failures. - UX: supports multiple connected subscription accounts per provider/namespace; makes it possible to pool/rotate accounts intentionally.
-
Architecture: keeps auth concerns behind provider adapters and a single
fetchintegration point, so it's provider-agnostic.
No pressure to merge — happy to iterate to match your preferences (split PRs, smaller surface area, rename/restructure) or you can close it and I'll keep it as a fork/local patch.
This issue might be a duplicate of existing issues. Please check:
- #5391: [FEATURE]: multiple auth profiles per provider - directly addresses multi-account support per provider
- #4318: [FEATURE]: Allow storage of secrets in system credential store - addresses encrypted credential storage
- #5423: [FEATURE]: Store provider credentials in environment variables - related to credential management and storage
- #4170: Reorganize and document dirs for config, agents, auth credentials, sessions - discusses auth credential storage and organization
Feel free to ignore if your proposal addresses aspects beyond these existing issues.
regarding a part of this:
how credentials are stored
Bun added support for native secrets api, we should probably just use that and I think we have an issue w someone working on that support
Thanks — re: credential storage, I agree Bun.secrets / system keyring is probably the right long-term direction (and I see #4318 is already tracking that).
My current branch uses encrypted record files on disk (AES-256-GCM) + a vault key loaded from env or generated locally, mainly for portability/headless environments + easy backup/restore.
If you’d prefer aligning with Bun.secrets, I can change the implementation to match #4318 in one of two ways:
-
A) Store each credential secret JSON directly in
Bun.secrets(serviceopencode, name based on record id/provider/namespace) and keep only non-secret metadata/pool/cooldowns on disk. -
B) Keep encrypted record files, but store the vault key in
Bun.secrets(with file/env fallback for CI/server).
Given someone is already working on #4318, I’d rather not duplicate effort — happy to either (1) refactor this to use a pluggable backend so it can swap to Bun.secrets, or (2) rebase/cherry-pick the rotation/multi-account parts on top of the eventual keyring PR.
Which approach would you prefer?
Re the possible-duplicate bot note: totally fair — this overlaps with a few threads.
- #5391 covers multi-account/auth profiles per provider (this proposal implements that as provider+namespace+label records + pool ordering/cooldowns).
- #4318 covers system keyring / Bun.secrets (I’m happy to align storage with that; see my comment above).
The additional piece this proposal tries to bring is the same-request rotation/refresh engine (Retry-After-aware 429 rotation + refresh-on-expired where supported) behind a single fetch integration point, plus the provider adapter registry so auth differences live in one place.
If you’d rather land this as smaller pieces that align with those issues, I’m happy to restructure.
@rekram1-node totally agree re: using Bun.secrets / system keyring for how secrets are stored (and +1 to aligning with #4318).
The main value I’m aiming for here is multi-account OAuth subscription failover (not API-key billing):
- Allow multiple OAuth “subscription” accounts per provider/namespace.
- On
429(Retry-After aware) rotate to the next account and retry within the same user request. - On expired auth (
401/403) refresh where supported; otherwise rotate. - Persist pool ordering + cooldowns so failover is stable across runs.
This is implemented as a fetch-level wrapper so most requests are transparent to the user; the known limitation is mid-stream rotation once tokens have started emitting — that still surfaces an error and the user retries (called out in the RFC).
Happy to rework the storage backend to use Bun.secrets while keeping the multi-account + rotation engine intact.
I created a new focused PR that aligns with the Bun.secrets/keychain direction and scopes to the core value (multi-account OAuth subscription failover):
- #5754 auth: multi-account OAuth subscription failover
Highlights:
- multiple OAuth accounts per provider
- same-request retry/failover on
429(Retry-After aware) - refresh-on-
401/403(force refresh once, else rotate) - secrets stored via
Bun.secrets(serviceopencode), disk stores only metadata/pool state
This avoids the larger auth-v2/UI/model-discovery/vault CLI scope.
@rekram1-node quick update — I took your feedback and narrowed this down to the core value, while avoiding overlap with the keyring work in #4318.
- Focused PR: #5754 (multi-account OAuth subscription failover)
- Only the OAuth secrets go into
Bun.secrets(serviceopencode); API-key / wellknown creds stay inauth.jsonfor now - Robust refresh persistence: when providers/plugins persist refreshed tokens via
/auth/:providerID,Auth.set()now updates the correct OAuth record by matching therefreshtoken (no API shape changes) - Rotation behavior is covered by focused tests (
429w/ Retry-After rotation +401refresh/rotate)
No pressure to merge — if this isn’t the direction you want in core, totally fine to close; feel free to cherry-pick the rotation bits if they’re useful.