Authentication & Authorization
Relevant source files
The following files were used as context for generating this wiki page:
- apps/api/spec/auth.md
- apps/api/src/modules/auth/interface/auth.controller.ts
- apps/api/src/modules/auth/interface/auth.pages.ts
- apps/api/src/infrastructure/http/mcp-auth.middleware.ts
- apps/api/src/infrastructure/http/app-router.ts
- apps/api/test/infrastructure/http/mcp-auth.middleware.test.ts
- apps/api/spec/bio.md
Authentication in thoth is managed by Better Auth, which operates as an OAuth 2.1 server for Model Context Protocol (MCP) clients. The system federates login to GitHub, ensuring the application never handles user passwords. It issues JWT access tokens to authorize requests for private tools and user-scoped data.
The system distinguishes between public tools, which allow anonymous access, and private tools, which require a valid Principal derived from a Bearer token. Authorization is enforced per-call within the JSON-RPC body of MCP requests.
Sources: apps/api/spec/auth.md:7-12, apps/api/spec/auth.md:23-28
System Architecture
The authentication architecture consists of three primary components: the Better Auth server, the MCP Authorization Middleware, and the GitHub identity provider.
Better Auth Server
Better Auth mounts at /api/auth and handles the OAuth 2.1 dance. It manages sessions, accounts, and JWKS (JSON Web Key Sets) used for token signing and verification.
Sources: apps/api/spec/auth.md:16-17, apps/api/src/modules/auth/interface/auth.controller.ts:13-14
MCP Authorization Middleware
This middleware gates the /mcp endpoint. It inspects incoming JSON-RPC calls to determine if the requested tool is private. If a private tool is requested without a valid Bearer token, the middleware returns a 401 Unauthorized response with a WWW-Authenticate header pointing to the resource metadata.
Sources: apps/api/src/infrastructure/http/mcp-auth.middleware.ts:33-56, apps/api/spec/auth.md:23-28
The diagram shows the decision flow for incoming MCP tool calls. Public tools bypass token checks, while private tools trigger a JWT validation or a 401 challenge.
Sources: apps/api/src/infrastructure/http/mcp-auth.middleware.ts:33-56, apps/api/spec/auth.md:23-28
Authentication Flow
When a client attempts to call a private tool without a token, the following sequence occurs:
- Initial Call: Client sends
POST /mcpwith a private tool call (e.g.,remember). - Challenge: thoth returns
401 Unauthorized. - Discovery: Client fetches metadata from
/.well-known/oauth-protected-resource. - Authorization: Client performs the OAuth dance against Better Auth at
/api/auth. - GitHub Login: Better Auth redirects the user to GitHub for authentication.
- Token Issuance: Better Auth issues a JWT to the client.
- Retry: Client retries the tool call with the JWT in the
Authorization: Bearer <token>header.
Middleware Logic and Gating
The mcpAuthMiddleware performs deep inspection of the request body. It supports both single JSON-RPC messages and batch arrays.
Gating Rules
- Public Routes:
GET /,GET /health, and endpoints under/api/authare public. - MCP Public Tools: Tools like
get_bioorinitializestay anonymous. - MCP Private Tools: Tools like
create_bio,update_bio, orrememberrequire aPrincipal. - Public-Read-Only Mount: A private tool called on a path with a handle (e.g.,
/mcp/:handle) returns404 Not Foundrather than401 Unauthorizedto prevent information leakage.
Sources: apps/api/spec/auth.md:16-20, apps/api/src/infrastructure/http/mcp-auth.middleware.ts:40-45, apps/api/spec/bio.md:52-53
Principal Extraction
If a valid token is provided, the middleware populates res.locals["principal"] with a Principal object containing:
userId: The unique identifier for the user.scopes: The authorized access levels.
Sources: apps/api/spec/auth.md:33-34, apps/api/src/infrastructure/http/mcp-auth.middleware.ts:51-54
API Surfaces & Configuration
Auth Endpoints
| Endpoint | Method | Description |
|---|---|---|
/api/auth/* | VARIES | Better Auth internal routes (sign-in, callback, etc.) |
/api/auth/sign-in | GET | Renders the HTML sign-in stub for GitHub federation. |
/api/auth/consent | GET | Renders the HTML consent stub for client authorization. |
/.well-known/oauth-protected-resource | GET | Returns metadata for MCP client discovery. |
| Sources: apps/api/src/modules/auth/interface/auth.controller.ts:42-60, apps/api/src/infrastructure/http/app-router.ts:74-78 |
Configuration Requirements
The system requires specific environment variables to function:
| Variable | Description |
|---|---|
GITHUB_CLIENT_ID | OAuth application ID from GitHub. |
GITHUB_CLIENT_SECRET | Secret key from GitHub. |
BETTER_AUTH_URL | Public base URL (also used as JWT audience). |
BETTER_AUTH_SECRET | Signing secret for internal auth processes. |
| Sources: apps/api/spec/auth.md:46-47 |
Data Security
Better Auth manages the following Postgres tables for persistence: user, session, account, verification, jwks, oauthClient, oauthAccessToken, oauthRefreshToken, and oauthConsent. Every user-scoped row in the application carries a userId, and every query filters results based on the authenticated Principal to ensure data isolation.
Sources: apps/api/spec/auth.md:41-43, apps/api/spec/auth.md:35-35
Authentication and authorization in thoth provide a secure, federated identity layer that allows personal data to be safely accessed and modified by MCP clients while maintaining public accessibility for non-sensitive tools.
Sources: apps/api/spec/auth.md:58-63