https://github.com/vymalo/opencode-oauth2
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.
https://github.com/vymalo/opencode-oauth2
ai authorization-code-flow bearer-token llm model-sync oauth2 oidc openai-compatible opencode opencode-plugin pkce plugin pnpm-workspace refresh-token typescript
Last synced: 4 days ago
JSON representation
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.
- Host: GitHub
- URL: https://github.com/vymalo/opencode-oauth2
- Owner: vymalo
- License: mit
- Created: 2026-05-24T07:59:52.000Z (6 days ago)
- Default Branch: main
- Last Pushed: 2026-05-25T01:33:03.000Z (5 days ago)
- Last Synced: 2026-05-25T01:34:09.409Z (5 days ago)
- Topics: ai, authorization-code-flow, bearer-token, llm, model-sync, oauth2, oidc, openai-compatible, opencode, opencode-plugin, pkce, plugin, pnpm-workspace, refresh-token, typescript
- Language: TypeScript
- Size: 267 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# opencode-oauth2
> Bring your own OAuth-protected LLM gateway to [OpenCode](https://opencode.ai).
An [OpenCode](https://opencode.ai) 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.




---
```mermaid
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]
```
## 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
```jsonc
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@vymalo/opencode-oauth2"]
}
```
Then declare a provider:
```jsonc
{
"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](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`](docs/architecture.md) | Understand the hooks, token lifecycle per flow, cache layout, sync scheduler, logging |
| [`docs/github-actions.md`](docs/github-actions.md) | CI without stored secrets — Keycloak/Auth0/Okta setup, reusable workflow, matrix, fork-PR limits |
| [`docs/kubernetes.md`](docs/kubernetes.md) | `CronJob` / `Job` / `Deployment` with projected SA tokens, multi-provider pods, RBAC |
| [`docs/local-development.md`](docs/local-development.md) | Sandbox setup, plugin re-export trick, forcing re-auth, dev-only `env` subject token |
| [`docs/troubleshooting.md`](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`](docs/github-actions.md) and [`docs/kubernetes.md`](docs/kubernetes.md). The shipped reusable workflow at [`.github/workflows/opencode-run.yml`](.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](https://pnpm.io) monorepo.
| Package | Purpose |
| --- | --- |
| [`packages/opencode-oauth2`](packages/opencode-oauth2) | The runtime plugin — published as `@vymalo/opencode-oauth2` |
| [`packages/plugin-bundle`](packages/plugin-bundle) | Rolldown-based bundling for distribution |
| [`plans/prd.md`](plans/prd.md) | Product requirements and phased roadmap |
## Development
```sh
pnpm install
pnpm build
pnpm typecheck
pnpm test
```
Plugin-only iteration:
```sh
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](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](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](LICENSE) © vymalo contributors