Login is solved. Supabase does it.

Auth is the kind of thing that sounds simple ("just log people in") and ends with a six month rewrite. We don't write auth. We use Supabase, which handles email login, Google SSO, magic links, password resets, multi factor, and the rest of the checklist. Our job is to consume the result.

What "auth" actually means

Three different jobs people confuse:

  1. Authentication: who are you? (Login, sessions, password reset.)
  2. Authorization: what are you allowed to do? (Can Juan talk to HR agent?)
  3. Connection: which third party tools is this agent allowed to act on? (Has Juan's Slack been linked?)

We use Supabase for #1. We do #2 ourselves in the control plane. #3 is mostly Composio (with help from us for the OAuth flow).

Supabase, the parts we use

How the JWT flows

01
User app.houston.ai
Clicks "log in." Frontend redirects to Supabase login UI.
02
User Supabase
Enters credentials (or clicks Google). Supabase authenticates.
03
Supabase User
Returns a JWT containing user ID, email, expiry. Frontend stores it (cookie or local storage).
04
Frontend Control plane
Every request includes the JWT in the Authorization header.
05
Control plane
Verifies the JWT's signature with Supabase's public key. Locally. No network call. Microseconds.
06
Control plane Postgres
Look up: which workspace + which permissions for this user ID? Cache the answer in Redis.

Authorization: who can do what

JWT only proves "you are Juan." Whether Juan can talk to the HR agent is a Houston question, answered from our permissions table in Postgres.

The rule is simple:

We considered fancier models (roles, groups, ABAC). For v1, a simple grant table covers 95% of needs. Add complexity when a customer asks.

OAuth for tools (Composio's job)

The third kind of auth: when the HR agent needs to read Juan's Slack messages, Slack has to grant permission. That's OAuth. Composio handles all the OAuth flows for the 1,000+ tools we integrate.

Flow:

  1. Juan asks HR agent to do something Slack related.
  2. Agent says "you haven't connected Slack yet, click here."
  3. Juan clicks. Composio opens Slack's OAuth dialog.
  4. Juan approves. Slack sends OAuth tokens back to Composio.
  5. Composio stores the tokens, keyed by (user, tool).
  6. Agent calls Composio with "send a message to Slack as Juan." Composio uses the stored tokens.

Composio tokens live in Composio's secure storage, not ours. We ask Composio to do things on a user's behalf using a Composio API key per workspace.

Sessions, in plain English

A "session" is the unbroken span from login to logout. Supabase manages it: when the JWT expires (default 1 hour), the frontend silently refreshes it using a longer-lived refresh token (30 days). User stays logged in for weeks unless they explicitly log out or the security policy says re-auth.

Enterprise tenants can tighten this: "force re-login every 8 hours" or "require MFA on each session." All handled by Supabase config per tenant.

What "partial" means in the status pill

We already use Supabase for the desktop app's "sign in to use a remote Houston engine" flow (it shipped in commit c9affe3f). That gives us the OAuth foundation. What we don't have yet: the multi-tenant permissions table, the enterprise SSO flow, the audit log of who-did-what. All additive on top of what exists.

Why we don't write our own auth

Auth is the bottomless pit. Password hashing edge cases, OAuth provider quirks, session fixation attacks, MFA bypass bugs, GDPR-compliant deletion. Supabase has a security team that does nothing but this. Our team gets to ship Houston instead of becoming part-time identity engineers.

Concrete pieces

Supabase project for the cloud (separate from the desktop auth project). JWT validation in the control plane using the project's JWKS URL. users table in our Postgres mirrors a subset of Supabase user data (email, workspace, role) for fast lookup. RLS not used directly — control plane enforces all authz in code so we keep Supabase as a pure identity provider.