Authentication & SSO
All DotEvolve products share a single identity provider: Supabase Auth. Users log in once and access all their assigned apps without re-authenticating.
JWT Structure
Every authenticated request carries a Supabase JWT. The app_metadata claims are set by dot-portal-api and injected into every token via a custom access token hook.
{
"sub": "uuid",
"email": "user@example.com",
"app_metadata": {
"activeTenantId": "uuid-A",
"activeRole": "tenant-admin",
"activeAppAccess": ["foot-factory", "dot-cos"],
"tenants": [
{
"tenantId": "uuid-A",
"tenantName": "Acme Corp",
"role": "tenant-admin",
"apps": ["foot-factory", "dot-cos"],
"appSubdomains": {
"foot-factory": "acme-corp.foot-factory.dotevolve.net",
"dot-cos": "acme-corp.cos.dotevolve.net"
}
}
]
}
}
Login Flow
sequenceDiagram
participant U as User
participant SB as Supabase Auth
participant PA as Portal API
U->>SB: Login (email + password)
SB->>SB: Run custom_access_token_hook
SB->>SB: Merge raw_app_meta_data into JWT claims
SB-->>U: JWT with app_metadata
U->>PA: API request (Bearer JWT)
PA->>SB: Validate JWT (public key)
SB-->>PA: Validated user + app_metadata
Active Tenant Switching
Admins who manage multiple tenants use the Tenant Picker to switch context:
- User selects a tenant card in the Admin Portal
- Admin Portal calls
POST /api/v1/users/me/active-tenantwith{ tenantId } dot-portal-apiupdatesapp_metadata.activeTenantId,activeRole,activeAppAccess- Admin Portal calls
supabase.auth.refreshSession()to get a new JWT - Admin Portal navigates to
/tenants/:tenantId
SSO Deep Links
dot-admin can generate SSO deep links into app dashboards (dot-foot-factory-admin, dot-cos-admin-dashboard). The link format is:
https://{app-subdomain}/auth/sso?token=<jwt>&tenantId=<id>
The receiving app validates the JWT via Supabase, sets the session, and redirects to its home page with the correct tenant context loaded.
Downstream JWT Validation
Both dot-foot-factory-api and dot-cos-workflow-service validate JWTs using the Supabase public key. They extract activeTenantId and activeRole from app_metadata claims — no HTTP call to any auth service is needed at request time.
// dot-foot-factory-api protect middleware
const {
data: { user },
error,
} = await supabase.auth.getUser(token);
const meta = user.app_metadata ?? {};
req.user = {
id: user.id,
email: user.email,
activeTenantId: meta.activeTenantId,
activeRole: meta.activeRole,
app_metadata: meta,
};
// dot-cos-api-gateway resolveTenant
const tenantId = req.user?.app_metadata?.activeTenantId;
req.tenantId = tenantId;
req.headers["x-tenant-id"] = tenantId;