Skip to main content

Authentication

info

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

  1. 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.
  2. Supabase returns a JWT (the access_token) plus a refresh_token.
  3. Client calls our API with Authorization: Bearer <access_token>.
  4. Our API verifies the token via supabase.auth.getUser(token) and attaches the resolved Supabase User to the Hono context as c.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>
FailureResponse
Missing header401 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:

TokenDefault TTL
access_token1 hour
refresh_tokenLong-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.