Skip to content

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

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,
  });
}