Skip to content

Auth and RPC Layers

This document explains how requests authenticate from clients through the API gateway down to PostgreSQL roles and database schemas.

Auth and RPC layers


The Big Picture (How Auth Works)

  1. Authentication: A user or application logs in via Keycloak and receives a signed JSON Web Token (JWT). The token contains the user's ID (sub) and an assigned database role claim (role).
  2. Routing: The API gateway routes requests to either PostgREST (direct data queries) or FastAPI (multi-step workflows).
  3. Database Authorization:
  4. PostgREST: Validates the JWT signature against Keycloak's public keys. It temporarily switches the database connection to the JWT's role and sets session variables (request.jwt.claims). PostgreSQL Row-Level Security (RLS) then automatically ensures callers only see data they are permitted to access.
  5. FastAPI: Validates the token in Python dependencies (get_current_principal or require_enterprise_identity). For trusted server-side writes, it connects directly via PostgreSQL pools (asyncpg) using a constrained service login. When querying PostgREST on behalf of a user, it forwards the user's bearer token.

Client Surfaces & Role Mapping

The platform isolates clients across three symmetric surfaces:

[Public Clients]       ──► Keycloak (vibrant)  ──► role: authenticated       ──► schema: api
[Enterprise Clients]   ──► Keycloak (vibrant)  ──► role: enterprise_portal_role ──► schema: enterprise_api
[Internal Staff/Ops]   ──► Keycloak (internal) ──► role: internal_api_role   ──► schema: internal_api

1. Public Surface

  • Audience: Mobile app users, residential customers, and contractors.
  • Keycloak Realm & Client: vibrant realm; vibrant-mobile client (customer and contractor roles).
  • Database Role: authenticated (unauthenticated requests fall back to anon).
  • Services: vibrant_platform_api (port 3400) and public PostgREST (port 3300) exposing the api schema.

2. Enterprise Surface

  • Audience: Commercial building managers, site maintenance teams, and enterprise partners.
  • Keycloak Realm & Client: vibrant realm; vibrant-enterprise-web client.
  • Database Role: enterprise_portal_role (anonymous requests fall back to enterprise_portal_anon).
  • Services: vibrant_platform_enterprise_api (port 3800) and enterprise PostgREST (port 3850) exposing the enterprise_api schema.

3. Internal Surface

  • Audience: Internal staff, factory operators, and internal automation.
  • Keycloak Realm & Client: internal realm; vibrant-internal-web (staff browser with MFA/TOTP) and vibrant-internal (service-to-service).
  • Database Role: internal_api_role (internal_anon has zero grants and fails closed).
  • Internal Realm Roles: internal_staff, internal_manufacturing, internal_manufacturing_admin, internal_admin.
  • Services: vibrant_platform_internal_api (port 3500) and internal PostgREST (port 3600) exposing the internal_api schema.

Database Roles & Helpers

Helper Functions (auth.*)

SQL views and RLS policies inspect caller identity using helper functions in infrastructure/db/migrations/20260831000100_shared_foundation.sql: * auth.uid() — Returns the UUID subject (sub) of the authenticated user. * auth.role() — Returns the current database role claim. * auth.jwt() — Returns the complete JSON payload of the JWT. * auth.claim(key) — Retrieves a specific claim from the JWT.

Authenticator Roles vs. Application Roles

  • authenticator, enterprise_portal_authenticator, and internal_postgrest_authenticator are connection roles used by PostgREST instances. They possess no table privileges; they only have permission to SET ROLE to authorized application roles.
  • platform_api_role, enterprise_api_role, and internal_api_role are constrained application roles used by the FastAPI services when connecting directly to PostgreSQL via asyncpg.

Rules for Adding Views & RPC Functions

  1. Expose Contracts, Not Tables: Keep domain tables private. Expose stable views and stored procedures (FUNCTION) in api, enterprise_api, or internal_api.
  2. Explicit Privilege Revocation: Always execute REVOKE EXECUTE ON FUNCTION ... FROM PUBLIC; before granting to authorized roles (authenticated, enterprise_portal_role, or internal_api_role).
  3. Security Invoker by Default: Views must declare WITH (security_invoker = true) so caller RLS policies are evaluated. Use SECURITY DEFINER only when bypassing RLS is explicitly required, and always pin SET search_path = pg_catalog.
  4. Hoisted Subqueries in Policies: When writing CREATE POLICY, wrap (SELECT auth.uid()) in scalar subqueries so PostgreSQL evaluates the identity once per statement rather than once per row.

Technical Pointers

  • Identity Contract: src/vibrant_platform_common/auth/identity_contract.py
  • Security & JWT Verification: src/vibrant_platform_common/core/security.py
  • Local PostgREST & Keycloak Config: infrastructure/local/compose.yaml
  • SQL Contracts & Coding Standards: docs/database/sql-contracts.md