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:
| Route | What |
|---|---|
GET /.well-known/oauth-authorization-server | The metadata (RFC 8414): the issuer (FOI_PUBLIC_URL), the endpoints below under it, PKCE S256, public clients. |
POST /oauth/register | Dynamic 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/authorize | The 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/authorize | The page's two forms; allowing sends the browser back to the client with a code and the state (and iss, RFC 9207). |
POST /oauth/token | authorization_code with its PKCE verifier (S256 only; plain and a missing verifier refused), refresh_token (rotated). |
POST /oauth/revoke | Revocation (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.