Auth
lib/auth (Rust kalt-auth) issues and verifies access JWTs. app/web/app/lib/auth is the browser session. svc/auth is the HTTP service behind /api/auth. There is no Supabase Auth.
Flow
- Type a phone number and submit.
POST /api/auth/initiatereturnsokid_pending. The app opens/id/pending/{okid}.svc/communicationsubscribes to that auth message and sends an SMS with{AUTH_ORIGIN}/id/{okid_approval}.- Opening that link loads
/id/{okid}. - Approve mints access and refresh tokens for a one-shot
POST /api/auth/claim. Decline stores nothing.
The TypeScript lib persists the claimed tokens and attaches Authorization: Bearer on later API calls. Public challenge routes never send a bearer, so a leftover token cannot block sign-in through the gateway.
Tokens
Access JWT (HS256, AUTH_JWT_SECRET), about 15 minutes:
{ "sub": "user-id", "role": "editor", "phone": "+47…", "exp": 1710000900 }
Refresh token is opaque, stored hashed in Postgres, rotated on POST /api/auth/refresh. signOut() revokes it.
Frontend
import { accessToken, claimAndInstall, signOut, withAuthHeaders } from '~/lib/auth'
const user = useAuthUser()
await claimAndInstall(okid)
const token = await accessToken()
await withAuthHeaders('/api/publications')
await signOut()
useAuthUser() and useIsEditor() (user.role === 'editor') read role from the access JWT. Public /api/auth and /api/geo paths skip Bearer.