Authentication — portal and MCP sessions
AppHandoff uses Supabase sessions for the first-party portal and OAuth for the MCP server. General REST API keys are retired; Context Capture has a separate private ticket credential, scoped either to one project or to a whole billing account.
Portal session (browser)
Section titled “Portal session (browser)”The portal uses Supabase session cookies after login. Session auth covers portal UI routes and SSE streams (e.g. ticket realtime).
Email + password
Section titled “Email + password”POST /api/auth/loginContent-Type: application/json
{ "email": "you@example.com", "password": "..." }200 response:
{ "token": "<access_token>", "refresh_token": "<refresh_token>", "expires_in": 28800, "user": { "id": "...", "email": "..." }}Use token as Authorization: Bearer <token> for API calls from your app.
Magic link
Section titled “Magic link”POST /api/auth/send-magic-link{ "email": "you@example.com" }Token refresh
Section titled “Token refresh”When the access token is about to expire or you receive a 401:
POST /api/auth/refresh{ "refresh_token": "<refresh_token>" }Returns a new token and optionally a new refresh_token.
OAuth (MCP clients)
Section titled “OAuth (MCP clients)”MCP clients must use OAuth. Discovery endpoints:
| Endpoint | Purpose |
|---|---|
GET /.well-known/oauth-protected-resource | Resource metadata (RFC 9728) |
GET /.well-known/oauth-authorization-server | Authorization server metadata (RFC 8414) |
The token_endpoint is on AppHandoff (/api/oauth/token), not Supabase’s host. Use exactly what discovery returns.
Refresh an MCP access token
Section titled “Refresh an MCP access token”POST /api/oauth/tokenContent-Type: application/json
{ "grant_type": "refresh_token", "refresh_token": "<token>", "client_id": "<uuid>"}Important: Do not request the offline_access scope — Supabase rejects it. Refresh tokens are issued unconditionally on the authorization code grant.
Every MCP response includes X-Token-Expires-In (seconds remaining). Refresh proactively when it drops below 300.
Auth matrix
Section titled “Auth matrix”| Surface | Auth method |
|---|---|
| Portal UI | Session cookie |
MCP (/mcp), including tools/list | OAuth or a personal access token (ah_pat_*) |
| Context Capture ticket API | ah_cc_* credential, project-bound or account-scoped |
To check an MCP credential end to end, call bootstrap: it verifies auth, server health, and caller context in one round-trip.
Common errors
Section titled “Common errors”| HTTP | Code | Meaning |
|---|---|---|
| 401 | — | Missing or invalid credentials |
| 401 | TOKEN_EXPIRED | MCP access token expired — refresh and retry |
| 401 | AUTH_REQUIRED | No bearer token or token rejected — re-run OAuth |
| 403 | — | Valid auth but insufficient access to the resource |
See Connect MCP for MCP-specific OAuth troubleshooting.