Skip to content

[FEATURE]: Support Bearer token authentication for opencode serve #24874

@Edison-A-N

Description

@Edison-A-N

Problem

Currently opencode serve only supports HTTP Basic Auth via OPENCODE_SERVER_PASSWORD. This creates friction in common deployment scenarios:

  1. Reverse proxy / API gateway integration — Most ingress controllers (nginx, Caddy, Traefik, Cloudflare Tunnel) and API gateways use Bearer tokens for upstream authentication. Operators must add a translation layer to convert Bearer → Basic.

  2. Programmatic access — External tools (CI pipelines, scripts, desktop apps) typically pass Authorization: Bearer <token> rather than encoding username:password in base64. The current Basic Auth requirement forces each client to know the username convention ("opencode").

  3. Credential leakage surface — Basic Auth embeds the password in every request header as reversible base64, whereas a Bearer token is opaque and easier to rotate/revoke without changing the underlying secret.

Related issues that reflect current auth pain points:

Current Implementation

From packages/opencode/src/server/middleware.ts:

export const AuthMiddleware: MiddlewareHandler = (c, next) => {
  if (c.req.method === "OPTIONS") return next()
  const password = Flag.OPENCODE_SERVER_PASSWORD
  if (!password) return next()
  const username = Flag.OPENCODE_SERVER_USERNAME ?? "opencode"
  if (c.req.query("auth_token"))
    c.req.raw.headers.set("authorization", `Basic ${c.req.query("auth_token")}`)
  return basicAuth({ username, password })(c, next)
}

The Authorization Effect layer (server.ts#L69-L83) only handles the basic scheme — no bearer branch exists.

Proposal

Add a new env var OPENCODE_SERVER_TOKEN that enables Bearer token auth:

# Option A: Bearer token only
OPENCODE_SERVER_TOKEN=my-secret-token

# Option B: Both schemes simultaneously (backward compatible)
OPENCODE_SERVER_PASSWORD=legacy-pass
OPENCODE_SERVER_TOKEN=my-secret-token

Expected behavior

Header Result
Authorization: Bearer <OPENCODE_SERVER_TOKEN> ✅ Authenticated
Authorization: Basic base64(user:OPENCODE_SERVER_PASSWORD) ✅ Authenticated (existing)
?auth_token=<OPENCODE_SERVER_TOKEN> ✅ Authenticated (query param)
No header, no token set ✅ Unsecured (existing)
Wrong token/password ❌ 401

Suggested middleware change

export const AuthMiddleware: MiddlewareHandler = (c, next) => {
  if (c.req.method === "OPTIONS") return next()

  const password = Flag.OPENCODE_SERVER_PASSWORD
  const token = Flag.OPENCODE_SERVER_TOKEN

  // No auth configured — pass through
  if (!password && !token) return next()

  const authHeader = c.req.header("authorization") ?? ""

  // Bearer token check
  if (token) {
    if (authHeader === `Bearer ${token}`) return next()
    if (c.req.query("auth_token") === token) return next()
  }

  // Fall back to Basic Auth
  if (password) {
    if (c.req.query("auth_token"))
      c.req.raw.headers.set("authorization", `Basic ${c.req.query("auth_token")}`)
    const username = Flag.OPENCODE_SERVER_USERNAME ?? "opencode"
    return basicAuth({ username, password })(c, next)
  }

  return c.text("Unauthorized", 401)
}

Changes needed

  1. Add OPENCODE_SERVER_TOKEN to flag.ts
  2. Update AuthMiddleware in middleware.ts to handle Bearer scheme
  3. Add bearer branch to the Effect-based Authorization layer in server.ts
  4. Update opencode attach --password to also accept --token flag
  5. Update the web command warning to mention both env vars

Alternatives Considered

  • Reuse OPENCODE_SERVER_PASSWORD as Bearer token — Conflates two auth schemes; clients can't know which header format to use.
  • Only support Bearer, drop Basic — Breaking change for existing deployments.
  • Reverse proxy workaround — Works but adds operational complexity for a common use case.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions