Authentication — Floorix
Floorix delegates all authentication to Supabase Auth. There are no local passwords, no custom JWTs, and no refresh tokens managed by Floorix itself.
How It Works
sequenceDiagram
participant U as User
participant SB as Supabase Auth
participant PA as Portal API
participant FF as Floorix API
U->>SB: Login (email + password)
SB-->>U: JWT with app_metadata
note over U,SB: app_metadata contains:\nactiveTenantId, activeRole,\nactiveAppAccess, tenants[]
U->>FF: API request (Bearer JWT)
FF->>SB: supabase.auth.getUser(token)
SB-->>FF: Validated user + app_metadata
FF->>FF: Extract activeTenantId, activeRole
FF->>FF: Scope all queries to activeTenantId
FF-->>U: Response (tenant-scoped data)
JWT Structure
Every request to Floorix API must include a valid Supabase JWT:
Authorization: Bearer <supabase_jwt>
The JWT app_metadata contains:
{
"activeTenantId": "uuid-of-active-tenant",
"activeRole": "tenant-admin",
"activeAppAccess": ["floorix"],
"tenants": [
{
"tenantId": "uuid",
"tenantName": "Acme Corp",
"role": "tenant-admin",
"apps": ["floorix"],
"appSubdomains": {
"floorix": "acme-corp.floorix.net"
}
}
]
}
protect Middleware
The protect middleware in floorix-api validates every incoming request:
// Simplified protect middleware
const {
data: { user },
error,
} = await supabase.auth.getUser(token);
if (error || !user) throw new AuthenticationError("Invalid or expired token");
// Check tenant status
const { data: tenant } = await supabaseAdmin
.from("tenants")
.select("status")
.eq("id", meta.activeTenantId)
.single();
if (tenant?.status === "suspended")
throw new AppError("Tenant is suspended", 403);
if (tenant?.status === "pending_payment")
throw new AppError("Payment required", 402);
req.user = {
id: user.id,
email: user.email,
activeTenantId: meta.activeTenantId,
activeRole: meta.activeRole,
app_metadata: meta,
};
Status Codes
| Tenant Status | HTTP Response |
|---|---|
active |
Request proceeds normally |
suspended |
403 Forbidden |
pending_payment |
402 Payment Required |
| Missing JWT | 401 Unauthorized |
| Expired JWT | 401 Unauthorized |
SSO Deep Links
The Admin Portal can generate SSO deep links into floorix-admin:
https://admin.floorix.net/auth/sso?token=<jwt>&tenantId=<id>
The /auth/sso route validates the JWT via Supabase, sets the session, and redirects to the dashboard with the correct tenant context.
Lazy Profile Creation
If a user's Supabase account exists but no MongoDB User document has been created yet (e.g. webhook delivery failed), the protect middleware creates the profile document on the first authenticated request:
let profile = await User.findOne({ supabaseId: user.id });
if (!profile) {
profile = await User.create({
supabaseId: user.id,
email: user.email,
name: user.email,
});
}