Authentication
Authentication is not implemented by this API. We delegate the entire login / signup / token-refresh flow to Supabase Auth (GoTrue). Our server only verifies the bearer JWT on each request.
How it works
- Client signs in directly against Supabase Auth using the Supabase JS SDK (or the GoTrue REST API). The client never hits our API for credentials.
- Supabase returns a JWT (the
access_token) plus arefresh_token. - Client calls our API with
Authorization: Bearer <access_token>. - Our API verifies the token via
supabase.auth.getUser(token)and attaches the resolved SupabaseUserto the Hono context asc.get("user").
The server-side verification middleware lives in
src/core/middleware/auth.middleware.ts:
const authHeader = c.req.header("Authorization");
if (!authHeader?.startsWith("Bearer ")) {
throw new UnauthorizedHTTPException();
}
const token = authHeader.slice(7);
const { data, error } = await supabase.auth.getUser(token);
if (error || !data.user) {
throw new UnauthorizedHTTPException();
}
c.set("user", data.user);
Client integration
TypeScript / browser
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
// Sign up
const { data, error } = await supabase.auth.signUp({
email: "user@example.com",
password: "securePassword123",
});
// Sign in
const { data: session, error: signInErr } =
await supabase.auth.signInWithPassword({
email: "user@example.com",
password: "securePassword123",
});
// Use the token against our API
const accessToken = session.session?.access_token;
await fetch("/profiles", {
headers: { Authorization: `Bearer ${accessToken}` },
});
The Supabase SDK handles refresh-token rotation automatically.
REST (no SDK)
POST /auth/v1/signup HTTP/1.1
Host: <SUPABASE_URL>
apikey: <SUPABASE_ANON_KEY>
Content-Type: application/json
{ "email": "user@example.com", "password": "securePassword123" }
POST /auth/v1/token?grant_type=password HTTP/1.1
Host: <SUPABASE_URL>
apikey: <SUPABASE_ANON_KEY>
Content-Type: application/json
{ "email": "user@example.com", "password": "securePassword123" }
GoTrue REST docs: supabase.com/docs/reference/javascript/auth-api
Bearer header contract
Every authenticated endpoint on this API expects:
Authorization: Bearer <supabase-access-token>
| Failure | Response |
|---|---|
| Missing header | 401 Unauthorized |
Header is not Bearer … | 401 Unauthorized |
Token rejected by supabase.auth.getUser() | 401 Unauthorized |
Errors are JSON: { "message": "Unauthorized" } (see
src/libs/errors.ts).
Token TTLs
These come from the Supabase project's auth settings. Defaults:
| Token | Default TTL |
|---|---|
access_token | 1 hour |
refresh_token | Long-lived, rotates on use |
Configure in Supabase Dashboard → Auth → Sessions.
Local development
For the local self-hosted Supabase stack, the auth endpoint is
http://localhost:8000/auth/v1/... (Kong → GoTrue). The dev script
api/devtools/authenticate.ts
mints a local access token interactively for use in *.http files:
bun run ./devtools/authenticate.ts
It prints the JWT to stdout; paste it into your editor's REST client.
What this API does not do
- ❌
/auth/login,/auth/register,/auth/refresh,/auth/verify— call Supabase directly. - ❌ Password hashing, email verification, magic-link emails — Supabase.
- ❌ OAuth provider redirects (Google, Apple, etc.) — Supabase.
What this API does do
- ✅ Verify the JWT on every authenticated route.
- ✅ Look up application-level user state (profile, friendships, etc.) keyed by the Supabase user ID.
- ✅ Apply business-rule authorization on top of the verified identity.