Skip to content

Sovereign Authentication & Authorization Flows

1. OpenIddict as Sovereign Identity Provider (IdP)

Section titled “1. OpenIddict as Sovereign Identity Provider (IdP)”

The GERCIA Platform operates OpenIddict running natively inside Gercia.Server as its sole central authority for identity, authorization, and token issuance. It eliminates third-party Identity-as-a-Service (BaaS) vendor lock-in, ensures zero-trust internal token validation, and maintains 100% data residency compliance under LGPD and CFM regulations.


Flow A: Internal GERCIA Staff (ops.gercia.com.br & ops-stg.gercia.com.br)

Section titled “Flow A: Internal GERCIA Staff (ops.gercia.com.br & ops-stg.gercia.com.br)”

Internal backoffice operations enforce Zero-Click Authentication governed by Cloudflare Zero Trust Access and Cloudflare Pages Functions:

sequenceDiagram
    autonumber
    actor Dev as GERCIA Staff Member
    participant CF_EDGE as Cloudflare Zero Trust Edge
    participant PAGES as Cloudflare Pages (/index.html)
    participant FUNC as Pages Function (/api/ops/me)
    participant AUTH as Angular AuthService (Signals)

    Dev->>CF_EDGE: Accesses ops.gercia.com.br (or ops-stg.gercia.com.br)
    Note over Dev,CF_EDGE: Cloudflare Access evaluates WARP Client + Google Workspace SSO (hd=gercia.com.br)
    CF_EDGE->>PAGES: Forwards authenticated request with CF-Access-Jwt-Assertion
    PAGES-->>Dev: Delivers Angular SPA Bundle (200 OK)
    Dev->>AUTH: App Bootstraps (AuthService.initAuth())
    AUTH->>FUNC: GET /api/ops/me
    FUNC->>FUNC: Validates cf-access-jwt-assertion & cf-access-authenticated-user-email
    FUNC-->>AUTH: HTTP 200 { authenticated: true, email: "user@gercia.com.br", ... }
    AUTH->>AUTH: Updates state signal: operator = { email, roles }
    AUTH-->>Dev: Renders Backoffice Dashboard Instantly (Zero Clicks)

In local developer environments (ops.gercia.dev:7250), AuthService detects loopback development mode and automatically provisions a mock development session (devsecops@gercia.com.br), eliminating Cloudflare Access dependencies during local testing.


Flow B: B2B Multi-Tenant Clients (app.gercia.com.br)

Section titled “Flow B: B2B Multi-Tenant Clients (app.gercia.com.br)”
sequenceDiagram
    autonumber
    actor User as Doctor / Pharmacist
    participant SPA as Angular SPA (@gercia/app)
    participant Server as Gercia.Server (OpenIddict STS)
    participant DB as PostgreSQL (core.users / tenant_user_accesses)

    User->>SPA: Submits Email + Password (or selects Enterprise SSO)
    SPA->>Server: POST /connect/token (OAuth 2.1 Code Flow with PKCE)
    Server->>DB: Verifies Argon2id password hash & TOTP 2FA
    Server->>DB: Resolves accessible tenants (TenantId, BranchId, Roles)
    Server-->>SPA: Returns Access Token (JWT 15m) + Refresh Token with tenant claims
    SPA-->>User: Renders tenant-scoped clinical/pharmacy dashboard

sequenceDiagram
    autonumber
    actor Patient as Mobile Patient
    participant App as GERCIA Care (KMP)
    participant Social as Google / Apple Identity
    participant Server as Gercia.Server (/connect/token)
    participant DB as PostgreSQL (core.user_logins)

    Patient->>App: Clicks "Sign in with Google / Apple"
    App->>Social: Prompts biometric authentication on smartphone
    Social-->>App: Returns signed id_token
    App->>Server: POST /connect/token (grant_type=social_token, id_token=...)
    Server->>Server: Validates token signature via native C# (Google.Apis.Auth)
    Server->>DB: Finds or provisions User in core.users & core.user_logins
    Server-->>App: Emits Access Token (15 min) + Refresh Token (30 days)
    App->>App: Stores in hardware SecureStorage (Keychain / Keystore)

Flow D: Hardware Station Agents (Gercia.Edge) & Hybrid Printing

Section titled “Flow D: Hardware Station Agents (Gercia.Edge) & Hybrid Printing”
sequenceDiagram
    autonumber
    participant Browser as Browser (@gercia/app)
    participant Edge as Local Gercia.Edge (127.0.0.1:5050)
    participant Server as Gercia.Server (Cloud Run)
    participant DB as PostgreSQL (core.print_jobs)

    rect rgb(240, 248, 255)
    Note over Browser,Edge: Bench Mode (Zero Cloud Latency)
    Browser->>Edge: GET /v1/scales/stream (CORS verified: app.gercia.com.br)
    Edge-->>Browser: Sub-millisecond SSE stream of live weigh telemetry (<1ms)
    Browser->>Edge: POST /v1/printers/raw (ZPL byte stream)
    Edge-->>Browser: Direct USB raw spooler execution (<50ms)
    end

    rect rgb(255, 250, 240)
    Note over Edge,DB: Centralized Print Server Mode (Outbox Long-Polling)
    Edge->>Server: GET /v1/print/jobs/poll (X-Station-Token)
    Server->>DB: Validates station token, checks pending jobs
    Server-->>Edge: Returns print payload (<200ms) or 204 No Content
    Edge->>Server: POST /v1/print/jobs/{id}/complete
    end

3. Host-Based Portal Session Segregation (.gercia.ops.session vs .gercia.app.session)

Section titled “3. Host-Based Portal Session Segregation (.gercia.ops.session vs .gercia.app.session)”

The enterprise frontend (@gercia/app) serves the internal operations platform (ops.gercia.com.br), the multi-tenant human healthcare & dental portal (app.gercia.com.br), and the sovereign veterinary portal (app.gercia.vet.br). To eliminate cross-portal session pollution and guarantee strict credential isolation even during local development on shared ports (via Cloudflare loopback DNS subdomains ops.gercia.dev, app.gercia.dev, and vet.gercia.dev resolving to 127.0.0.1), portal resolution is strictly host-driven (rejecting query parameter overrides), and authentication schemes are decoupled at the OpenIddict STS gateway level.

flowchart TD
    subgraph INGRESS ["1. Strict Host Ingress (Cloudflare DNS)"]
        REQ_OPS["Host: ops.gercia.com.br / ops.gercia.dev"]
        REQ_APP["Host: app.gercia.com.br / app.gercia.dev"]
        REQ_VET["Host: app.gercia.vet.br / vet.gercia.dev"]
    end

    subgraph ROUTING ["2. Dynamic Policy Scheme Selector"]
        SELECTOR["AddPolicyScheme ('GerciaSession')<br>ForwardDefaultSelector(PortalContext.ResolvePortalType)"]
    end

    subgraph SCHEMES ["3. Isolated Cryptographic Cookie & Token Schemes"]
        OPS_COOKIE["GerciaOpsCookie<br>Cookie: .gercia.ops.session<br>Identity: Google Workspace OIDC (@gercia.com.br)<br>Claims: NameIdentifier, Name, picture, OpsAdmin"]
        APP_COOKIE["GerciaAppCookie<br>Cookie: .gercia.app.session / Bearer JWT<br>Identity: B2B Sovereign STS (OpenIddict)<br>Claims: NameIdentifier, tenant_id, branch_id, Roles, vertical"]
    end

    REQ_OPS --> SELECTOR
    REQ_APP --> SELECTOR
    REQ_VET --> SELECTOR
    SELECTOR -- "IsOps == true" --> OPS_COOKIE
    SELECTOR -- "IsApp || IsVet == true" --> APP_COOKIE
  1. Dynamic Policy Selector (GerciaSession): The primary authentication scheme delegates to PortalContext.ResolvePortalType at runtime, resolving .gercia.ops.session for operational backoffice requests and .gercia.app.session for B2B client workloads (app.gercia.com.br and app.gercia.vet.br).
  2. Google OIDC Explicit Binding: Google Workspace OAuth 2.0 configuration sets SignInScheme = OpsCookieScheme. Successful authentication tickets write strictly to .gercia.ops.session.
  3. Atomic Multi-Scheme Logout: The /logout endpoint invokes SignOutAsync across both OpsCookieScheme and AppCookieScheme simultaneously, purging all lingering browser session cookies on the active origin.
  4. Permanent Backdoor Elimination: Development bypass endpoints (such as /login-dev-admin) are strictly forbidden and permanently expurgated from code, guaranteeing zero-trust enforcement across all runtime environments.
  5. Ops Ingress Authorization & Restrictive Access Boundary: Users authenticating via Google Workspace must belong to the corporate domain (@gercia.com.br) and possess authorized operator roles (governed through the ops@gercia.com.br corporate group). If an authenticated corporate identity lacks active operator privileges, execution is intercepted, suppressing internal navigation bars and presenting a dedicated, styled Restricted Access view instructing the user to request inclusion in ops@gercia.com.br.
  6. OIDC PKCE Protocol Standard (angular-auth-oidc-client): In the Angular SPA, authentication challenges utilize standard OAuth 2.1 Authorization Code Flow with Proof Key for Code Exchange (PKCE). Top-level redirects to external identity providers (Google Workspace OIDC) or sovereign OpenIddict login endpoints are handled cleanly via browser navigation, storing short-lived cryptographic tokens in browser memory with automatic silent token refresh.

Section titled “4. Defense-in-Depth Against Cookie Tampering, IDOR & Cross-Tenant Intrusion”

The platform rejects perimeter-only authentication models. Even if an attacker manipulates browser storage or crafts malicious payloads, cross-tenant data exfiltration and cross-platform privilege escalation are mathematically prevented across 4 independent security boundaries:

flowchart TD
    subgraph ATTACK ["Threat Vectors"]
        A1["1. Cookie Payload Tampering<br>(Attempting to inject tenant_id or OpsAdmin)"]
        A2["2. Cross-Subdomain Cookie Replay<br>(Renaming .gercia.ops to .gercia.app)"]
        A3["3. URL Parameter & Header IDOR<br>(Injecting ?tenant_id=... or X-Tenant-Id)"]
    end

    subgraph L1 ["Layer 1: Cryptographic Authentication"]
        D1["ASP.NET Core Data Protection API<br>HMAC-SHA256 signature + AES-256 encryption<br>Single-bit mutation invalidates MAC instantly"]
    end

    subgraph L2 ["Layer 2: Sovereign Tenant Resolution"]
        D2["WebTenantProvider<br>Reads tenant_id exclusively from decrypted session claims<br>Zero-trust on query strings, route tokens, and headers"]
    end

    subgraph L3 ["Layer 3: Application ORM Isolation"]
        D3["EF Core Global Query Filters<br>Appends WHERE tenant_id = @current_tenant_id<br>Missing claim evaluates to Guid.Empty (0 matches)"]
    end

    subgraph L4 ["Layer 4: Kernel Database Security"]
        D4["Google Cloud SQL Kernel RLS<br>FORCE ROW LEVEL SECURITY<br>USING (tenant_id = current_setting('app.current_tenant_id'))"]
    end

    A1 -- "HMAC validation failure" --> D1 -- "HTTP 401 Unauthorized" --> BLOCK1["Rejected at Kestrel Gateway 🛑"]
    A2 --> D2
    A3 --> D2
    D2 -- "No tenant_id in Ops cookie" --> D3
    D3 --> D4 -- "Cloud SQL drops tuples at disk level" --> BLOCK2["Zero Records Returned (0 Rows) 🔒"]
Threat Vector Targeted Vulnerability Mitigating Architecture Layer Result & Security Guarantee
Client-Side Cookie Modification Tampering with tenant_id or role claims inside browser storage Layer 1: ASP.NET Core Data Protection Cookies are opaque ciphertext signed via HMAC-SHA256. Tampering invalidates the MAC signature; request is rejected with HTTP 401.
Cross-Platform Session Replay Replaying an authenticated Ops cookie against app.gercia.com.br Layer 2: WebTenantProvider & core.tenant_user_accesses Ops cookie lacks tenant_id claim. TenantId resolves to Guid.Empty. User has no entry in tenant_user_accesses, resulting in 0 rows.
Insecure Direct Object Reference (IDOR) Injecting target clinic ID via ?tenant_id=... or custom header Layer 2 & Layer 3: Sovereign Resolution & Query Filters Ingress parameters are ignored. TenantId is derived strictly from server-signed session claims.
Application Query Leak (Missing WHERE) Developer omission of tenant filter in complex LINQ or raw SQL query Layer 4: Google Cloud SQL Kernel RLS PostgreSQL engine enforces FORCE ROW LEVEL SECURITY. Tuples matching another tenant are physically omitted by the kernel query planner.
Cross-Site Scripting (XSS) Token Theft Malicious script attempting to extract active session tokens Cookie Security Flags Cookies enforce HttpOnly = true (inaccessible to JavaScript document.cookie) and SameSite = Lax (CSRF prevention).
Man-in-the-Middle (MitM) Eavesdropping Plaintext transport sniffing over public networks Transport Layer Security Cloudflare Zero Trust Tunnels enforce TLS 1.3 encryption with Secure = Always cookie flag.
Post-Revocation Token Reuse Stolen session cookie used after employee termination or password reset Session Expiration & Token Revocation Ephemeral session lifetimes (8-hour sliding expiration) and immediate revocation of OpenIddict token grants upon credential modification or account deactivation (IsActive = false).