How the CentraKit API authenticates browser sessions and bearer tokens, and how clients get tokens through Supabase OAuth 2.1, PKCE and dynamic client registration.
The CentraKit API trusts one identity provider: Supabase Auth. A request is authenticated either by a Supabase session cookie (browsers) or by a Supabase access token sent as a bearer token (scripts, the CLI and MCP clients). Both paths end in the same request context, so permissions, Row Level Security, audit logging and errors behave the same way.
The SaaS app signs users in with Supabase and stores the session in the sb-auth-token cookie. Supabase splits large sessions into chunked cookies such as sb-auth-token.0. When a request to the API has no Authorization: Bearer header, the API reads this cookie session.
Cookies suit same-site browser flows. Use bearer tokens for everything else.
Send a Supabase access token (a JWT) in the Authorization header:
GET /api/v1/users/me HTTP/1.1
Host: api.centrakit.com
Authorization: Bearer <access-token>Before the route runs, the API verifies:
https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json;https://<project-ref>.supabase.co/auth/v1;authenticated, the MCP resource URL https://api.centrakit.com/api/mcp, or the MCP_RESOURCE_AUDIENCE value a deployment configures.As soon as the request has a Bearer authorization scheme, the cookie is ignored. An invalid or expired token gets 401 with an AUTH_INVALID_TOKEN problem and a WWW-Authenticate header that points to the protected resource metadata. The API does not fall back to the cookie session.
Supabase access tokens usually carry no CentraKit scopes. When a token does carry mcp:read or mcp:write in its scope claim, the REST API enforces them:
| Request | Required scope |
|---|---|
GET and HEAD | mcp:read |
POST /organizations/{organizationId}/tasks and PATCH .../tasks/{taskId}/status | mcp:read |
| Every other method | mcp:write |
A token without the required scope gets 403 with code AUTH_INSUFFICIENT_SCOPE. Scopes only narrow what a token can do. The user's organization permissions still decide every request. The MCP server applies its own, stricter per-tool scope check; see MCP server.
centrakit login runs an OAuth 2.1 authorization code flow with PKCE against Supabase:
state.https://<project-ref>.supabase.co/auth/v1/oauth/authorize in your browser with the challenge and a loopback redirect URI, http://127.0.0.1:8976/callback by default.state and exchanges the code and verifier at /auth/v1/oauth/token.GET /api/v1/users/me with the new token and stores the access and refresh tokens in session.json (file mode 600). It refreshes the access token when it expires.Browser login needs an OAuth client ID (CENTRAKIT_OAUTH_CLIENT_ID) and the Supabase URL (CENTRAKIT_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_URL). See CLI for every setting.
The Supabase project runs an OAuth 2.1 authorization server with dynamic client registration enabled. A client that wants to call the API on behalf of a user:
authorization_servers.MCP clients do all of this automatically when you add the server.
For scripts and CI, pass an access token you obtained earlier:
CENTRAKIT_TOKEN=<access-token> centrakit orgs list
curl -H "Authorization: Bearer $CENTRAKIT_TOKEN" https://api.centrakit.com/api/v1/organizationsAccess tokens expire. Long-running jobs need to refresh them with the refresh token from the same flow.
Not yet available: organization or personal API keys. Use OAuth tokens instead.
The API publishes RFC 9728 protected resource metadata:
| Environment | URL |
|---|---|
| Production | https://api.centrakit.com/.well-known/oauth-protected-resource |
| Local | https://api.localhost/.well-known/oauth-protected-resource |
{
"resource": "https://api.centrakit.com/api/mcp",
"authorization_servers": ["https://<project-ref>.supabase.co/auth/v1"]
}The document adds scopes_supported only when the deployment sets MCP_REQUIRED_SCOPES. The endpoint answers CORS preflight requests so browser-based clients can read it.
Authentication failures are problem details with an AUTH_* code:
| Code | Status | When |
|---|---|---|
AUTH_UNAUTHENTICATED | 401 | No session and no bearer token on a route that needs one. |
AUTH_INVALID_TOKEN | 401 | The bearer token failed verification or expired. |
AUTH_INVALID_CREDENTIALS | 401 | Wrong email or password on /auth/login. |
AUTH_INSUFFICIENT_SCOPE | 403 | The token lacks a required mcp:* scope. |
AUTH_CONFIRMATION_REQUIRED | 403 | The account email is not confirmed yet. |
AUTH_VALIDATION_ERROR | 400 | The request body is invalid. |
AUTH_PROVIDER_ERROR | 502 | Supabase Auth failed. |
AUTH_RESOURCE_SERVER_MISCONFIGURED | 500 | The deployment's audience or issuer configuration is wrong. |
A valid user without the organization permission for a route gets 403 with ORGANIZATION_PERMISSION_DENIED. See Errors.
Last updated on