opencode icon indicating copy to clipboard operation
opencode copied to clipboard

Proposal: Provider Auth v2 (encrypted credential vault + multi-account OAuth + same-request rotation)

Open jroth1111 opened this issue 4 weeks ago • 8 comments

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 429 within 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/403 when supported
    • persist pool ordering + cooldowns
  • 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 /models with 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

  1. Is this direction acceptable for OpenCode?
  2. Would you prefer the work reviewed as split PRs or as a single PR?
  3. Any preferences on vault key location / migration strategy / naming before I iterate further?

jroth1111 avatar Dec 18 '25 14:12 jroth1111

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 fetch integration 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.

jroth1111 avatar Dec 18 '25 14:12 jroth1111

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.

github-actions[bot] avatar Dec 18 '25 14:12 github-actions[bot]

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

rekram1-node avatar Dec 18 '25 15:12 rekram1-node

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 (service opencode, 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?

jroth1111 avatar Dec 18 '25 15:12 jroth1111

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.

jroth1111 avatar Dec 18 '25 15:12 jroth1111

@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.

jroth1111 avatar Dec 18 '25 15:12 jroth1111

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 (service opencode), disk stores only metadata/pool state

This avoids the larger auth-v2/UI/model-discovery/vault CLI scope.

jroth1111 avatar Dec 18 '25 15:12 jroth1111

@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 (service opencode); API-key / wellknown creds stay in auth.json for now
  • Robust refresh persistence: when providers/plugins persist refreshed tokens via /auth/:providerID, Auth.set() now updates the correct OAuth record by matching the refresh token (no API shape changes)
  • Rotation behavior is covered by focused tests (429 w/ Retry-After rotation + 401 refresh/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.

jroth1111 avatar Dec 18 '25 16:12 jroth1111