@anyknown/oauth-relay
v0.1.2
Published
Optional stateless OAuth authorization-code relay for local-first clients
Downloads
340
Readme
@anyknown/oauth-relay
An open-source, auditable Hono sub-app for handing an OAuth authorization code back to a local client. It is intentionally not an identity service, deployment, token exchange service, or token vault.
MCP hub works fully offline; the relay is optional and stateless.
Anyknown chooses a PKCE localhost loopback flow whenever a provider supports it. The relay is used only when a provider forbids localhost redirect URIs or when a shared Anyknown client_id requires a fixed callback. Token exchange happens directly between the CLI and the provider, and credentials stay on the user's machine.
Mount the sub-app
import {createOAuthRelay, type HandoffStore} from "@anyknown/oauth-relay"
import {Hono} from "hono"
const store: HandoffStore = createHostHandoffStore()
const app = new Hono()
app.route("/oauth", createOAuthRelay({store}))
export default appThe mounted routes are:
GET /oauth/callback— validates relay state and an optional return URL, then creates a short-lived authorization-code handoff.POST /oauth/redeem— verifies the original state and PKCE verifier, atomically consumes the handoff, and returns the code once.
The built-in InMemoryHandoffStore is suitable for local development and single-isolate demos. It lazily removes expired records and defaults to a hard 10,000-handoff capacity, but it is not shared across isolates. Production hosts inject a HandoffStore implementation. Its create and verified redeem operations must be atomic; raw eventually-consistent KV is not enough for one-time redemption. A Cloudflare host should serialize KV access with a Durable Object or use another compare-and-delete primitive.
Handoff contract
The CLI creates an encoded RelayStatePayload containing:
- contract version
1 - a random handoff ID and nonce
- the S256 PKCE challenge
- issue and expiry timestamps
- an optional browser return URL
The callback stores only the provider authorization code, the state digest, the challenge, and timestamps. A handoff lives for at most 60 seconds after callback. Redeem sends the original encoded state and code_verifier; a matching request atomically deletes and returns the code. Wrong verifiers do not consume a valid handoff. Callback redirects never contain the provider code.
The relay never receives or stores access tokens or refresh tokens. Responses use Cache-Control: no-store, callback HTML has a restrictive CSP, remote return URLs require an explicit HTTPS-origin allowlist, and the default callback return policy permits only IP loopback HTTP (127.0.0.1 or [::1]).
Client API
The package also exports the shared contract and client helpers:
selectOAuthPath— loopback by default; relay only for provider restrictions or shared clientsprepareOAuthFlow— creates state and PKCE parameterspollRelayHandoff/validateLoopbackCallback— obtains the authorization codeexchangeAuthorizationCode— exchanges the code directly with the provider over HTTPS
Treat a prepared flow as secret-bearing process memory. Its PKCE verifier is deliberately non-enumerable so accidental JSON serialization and journals do not include it.
Local workerd demo
From the repository root:
pnpm --filter @anyknown/oauth-relay-demo devThis starts a Vite shell on http://127.0.0.1:4175 and a local Wrangler/workerd process on port 8788. It is a fixture only and has no deployment command.
Host responsibilities
A production host supplies atomic ephemeral storage, rate limiting, abuse controls, provider OAuth app configuration, and deployment. It should mount this package at /oauth, retain the 60-second maximum handoff TTL, and never log callback query strings, redeem bodies, authorization codes, PKCE verifiers, or credentials.
