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 project-bound private ticket credential.
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 (/api/mcp-bot) | OAuth only |
| MCP tools list (/api/mcp-bot/tools) | Session or Bearer |
| Context Capture ticket API | Project-bound ah_cc_* credential |
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.