Skip to content

five-on-it / apps/api/src/oauth

apps/api/src/oauth ​

OAuth 2.1 for the MCP server (TECHNICAL_SPECS §9.6): the API is the authorization server Claude Code signs a creator in with, so claude mcp add --transport http five-on-it https://<site>/mcp needs no pasted token. What @modelcontextprotocol/sdk's Express handlers do (server/auth/handlers: authorize, token, register, revoke, the metadata), ported to Fastify routes, with the API's own accounts behind the sign-in page:

RouteWhat
GET /.well-known/oauth-authorization-serverThe metadata (RFC 8414): the issuer (FOI_PUBLIC_URL), the endpoints below under it, PKCE S256, public clients.
POST /oauth/registerDynamic client registration (RFC 7591): public clients only (token_endpoint_auth_method: none), their redirect URIs https, or http on a loopback address (Claude Code's local callback).
GET /oauth/authorizeThe sign-in page: the creator's e-mail and password (the accounts POST /auth/login signs in, dev or Nakama), or "Sign in through Steam" where Steam is on (its OpenID round trip comes back here with a one-time steam_code), then the consent ("<client> wants to make games as you"). With FOI_ENTITLEMENT=steam the page has no e-mail form, and the form's posts are refused.
POST /oauth/authorizeThe page's two forms; allowing sends the browser back to the client with a code and the state (and iss, RFC 9207).
POST /oauth/tokenauthorization_code with its PKCE verifier (S256 only; plain and a missing verifier refused), refresh_token (rotated).
POST /oauth/revokeRevocation (RFC 7009): an access or a refresh token ends its whole grant.

An access token is the API's own session for the account (Auth.issue), an hour long, so every route takes it as it takes any session; it names its grant, which the API checks on every request, so a revoked grant's tokens stop at once. A refresh token lasts thirty days and is used once: the answer carries the next, and a refresh token used twice (stolen, or replayed) revokes its grant. An authorization code lasts ten minutes and is used once: a code used twice revokes what it gave. Codes and refresh tokens are kept by the SHA-256 of their values, never the values. Errors before the client and its redirect URI are known are shown on the page; after, they go back to the client as error and error_description. The pages are plain HTML with their CSS inline, every value escaped, never framed (frame-ancestors 'none'). Registering, signing in and asking for tokens are limited per address, e-mail and client.

Interfaces ​

Variables ​

Functions ​