Skip to content

vymalo/opencode-oauth2

Repository files navigation

opencode-oauth2

Bring your own OAuth-protected LLM gateway to OpenCode.

An OpenCode plugin that lets you wire up OpenAI-compatible model providers sitting behind OAuth2 / OIDC — without baking long-lived API keys into your config. Discover models dynamically, refresh tokens automatically, and let OpenCode talk to your gateway as if it were any other provider.

status: early node: >=20 license: MIT pnpm workspace


flowchart LR
    OC[opencode] -->|chat.headers| Plugin[opencode-oauth2]
    Plugin -->|cached token?| Cache[(~/.cache/opencode-oauth2)]
    Plugin -->|acquire / refresh| IdP[OAuth server]
    Plugin -->|Authorization: Bearer …| Upstream[Provider API]
Loading

Why

Most OpenCode providers assume a static bearer key. That works for hosted SaaS, but breaks down the moment you put your models behind:

  • a corporate Identity Provider (Keycloak, Auth0, Okta, Azure AD, …)
  • a self-hosted gateway with short-lived tokens
  • a multi-tenant setup where each user authenticates as themselves
  • a CI runner that has no business carrying a long-lived secret

This plugin closes that gap. It handles the OAuth dance for the flow you need, caches tokens, refreshes silently, and feeds OpenCode a normal-looking provider with a fresh Authorization header on every request.

Features

  • Five auth flows, pick what matches your runtime:
    • authorization_code — interactive PKCE login (default)
    • device_code — RFC 8628, for browserless user auth
    • client_credentials — machine-to-machine with a clientSecret
    • jwt_bearer — RFC 7523 federated identity (GitHub Actions OIDC, Kubernetes SA tokens) — no long-lived secret in CI
    • token_exchange — RFC 8693 federated identity with explicit audience targeting
  • Dynamic model discovery from /v1/models (no hand-maintained model lists)
  • Display-name normalization so glm-5 shows up as GLM 5
  • Persistent token cache with automatic refresh
  • chat.headers hook injects bearer tokens per request
  • Two configuration styles: per-provider options or a top-level plugin block

Install

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["@vymalo/opencode-oauth2"]
}

Then declare a provider:

{
  "plugin": ["@vymalo/opencode-oauth2"],
  "provider": {
    "example-ai": {
      "name": "Example AI",
      "options": {
        "baseURL": "https://api.example.com/v1",
        "oauth2": {
          "issuer": "https://auth.example.com",
          "clientId": "opencode-client",
          "scopes": ["openid", "profile", "offline_access"],
          "syncIntervalMinutes": 60
        }
      }
    }
  }
}

See packages/opencode-oauth2/README.md for the full configuration reference (including the alternative pluginConfig.oauth2ModelSync.servers layout and every optional field).

Documentation

Page When you need it
docs/architecture.md Understand the hooks, token lifecycle per flow, cache layout, sync scheduler, logging
docs/github-actions.md CI without stored secrets — Keycloak/Auth0/Okta setup, reusable workflow, matrix, fork-PR limits
docs/kubernetes.md CronJob / Job / Deployment with projected SA tokens, multi-provider pods, RBAC
docs/local-development.md Sandbox setup, plugin re-export trick, forcing re-auth, dev-only env subject token
docs/troubleshooting.md Symptom-keyed fixes — redirect_uri_mismatch, model discovery 403, invalid_client, projected-token rotation

Federated identity (CI / Kubernetes)

For GitHub Actions and Kubernetes workloads, use jwt_bearer (or token_exchange) with the platform's own short-lived OIDC token as the subject. The plugin re-fetches it on every access-token expiry; nothing long-lived gets cached.

End-to-end recipes live in docs/github-actions.md and docs/kubernetes.md. The shipped reusable workflow at .github/workflows/opencode-run.yml covers the common opencode run case.

Token Policy

Refresh tokens are mandatory for the flows that issue them.

  • authorization_code / device_code exchanges that don't return refresh_token are rejected.
  • Cached tokens missing refreshToken are evicted on load (unless they're from client_credentials / jwt_bearer / token_exchange, which don't issue one).
  • Refresh responses that omit a new refresh_token re-use the existing one.

The intent: a user-flow session is either fully renewable or it doesn't get cached. Machine flows re-acquire on every expiry; refresh tokens have no role there.

Workspace Layout

This is a pnpm monorepo.

Package Purpose
packages/opencode-oauth2 The runtime plugin — published as @vymalo/opencode-oauth2
packages/plugin-bundle Rolldown-based bundling for distribution
plans/prd.md Product requirements and phased roadmap

Development

pnpm install
pnpm build
pnpm typecheck
pnpm test

Plugin-only iteration:

pnpm --filter @vymalo/opencode-oauth2 test
pnpm --filter @vymalo/opencode-oauth2 build

For end-to-end usage against a local OpenCode install, see GETTING_STARTED.md.

Status

Early but functional. The Phase 1 scaffold and Phase 2 runtime core are in; bundling (Phase 3) has landed. Public API may still shift before 1.0.

Roadmap and phase breakdown live in plans/prd.md.

Contributing

Issues and PRs are welcome. Please open an issue first for substantial changes so we can align on scope before code review.

License

MIT © vymalo contributors

About

OpenCode plugin that secures OpenAI-compatible model providers with OAuth2/OIDC (Authorization Code + PKCE), auto-discovers and syncs models from /v1/models, and injects bearer tokens per request — with strict refresh-token enforcement.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors