Skip to content

Technology

Sergio Hernandez edited this page Jul 18, 2026 · 8 revisions

Technology

TrackHub ensures secure and seamless integration across its ecosystem by implementing the Authorization Code Flow with Proof Key for Code Exchange (PKCE) via OpenIddict as the authorization method for both frontend and backend services. Built with React 19 + TypeScript on Vite, the frontend delivers a responsive user experience, while the backend services leverage GraphQL (via HotChocolate) and REST Minimal APIs to address specific integration needs.


Technology Stack

graph TB
    subgraph Frontend["Frontend Layer"]
        react["React 19 + TypeScript"]
        vite["Vite + Vitest"]
        mui["Material UI (MUI)"]
        maps["Leaflet / Google Maps"]
        tanstack["TanStack Query"]
        apollo["GraphQL Client<br/><i>codegen'd documents</i>"]
    end

    subgraph Auth["Authentication"]
        pkce["OAuth 2.0 + PKCE"]
        oiddict["OpenIddict"]
        jwt["JWT Bearer Tokens"]
    end

    subgraph Backend["Backend Services"]
        dotnet[".NET 10"]
        hc["HotChocolate (GraphQL)"]
        minapi["Minimal APIs (REST)"]
        cqrs["Custom CQRS Mediator"]
        fv["FluentValidation"]
    end

    subgraph Data["Data Layer"]
        pg["PostgreSQL 14+"]
        postgis["PostGIS Extension"]
        ef["Entity Framework Core"]
    end

    subgraph Infra["Infrastructure"]
        docker["Docker + Compose"]
        nginx["Nginx Reverse Proxy"]
        polly["Polly (Resilience)"]
    end

    react --> tanstack --> apollo --> hc
    react --> pkce --> oiddict
    oiddict --> jwt --> hc & minapi
    hc --> cqrs --> ef --> pg
    minapi --> cqrs
    pg --- postgis
Loading

Authentication Flow (PKCE)

TrackHub uses the Authorization Code Flow with PKCE for all public clients (web and mobile). This eliminates the need to store client secrets in the browser while maintaining security through a cryptographic code verifier.

sequenceDiagram
    participant User as User (Browser)
    participant App as TrackHub Web<br/>(React)
    participant Auth as AuthorityServer<br/>(OpenIddict)
    participant API as Backend Services

    Note over App: Generate code_verifier (random)<br/>Compute code_challenge = SHA256(code_verifier)

    App->>Auth: GET /Identity/authorize<br/>response_type=code<br/>client_id=web_client<br/>code_challenge=...<br/>code_challenge_method=S256<br/>scope=web_scope<br/>redirect_uri=.../callback

    Auth->>User: Login Page
    User->>Auth: Username + Password

    Auth-->>App: 302 Redirect → /callback?code=AUTH_CODE

    App->>Auth: POST /Identity/token<br/>grant_type=authorization_code<br/>code=AUTH_CODE<br/>code_verifier=ORIGINAL_VERIFIER<br/>redirect_uri=.../callback

    Note over Auth: Verify: SHA256(code_verifier) == code_challenge

    Auth-->>App: { access_token, refresh_token, expires_in }

    App->>API: GraphQL Request<br/>Authorization: Bearer {access_token}
    API->>Auth: Validate JWT Signature + Audience
    API-->>App: Response Data

    Note over App: When token expires...

    App->>Auth: POST /Identity/token<br/>grant_type=refresh_token<br/>refresh_token=...
    Auth-->>App: { new_access_token, new_refresh_token }
Loading

OAuth Clients

Client Type Scope Usage
web_client Public web_scope Web browser application
mobile_client Public mobile_scope Mobile application
sync_worker_client Confidential service_scope Backend service-to-service

Key Endpoints

Endpoint Purpose
/Identity/authorize Start authorization flow
/Identity/token Exchange code for tokens / refresh tokens
/Identity/revoke Revoke a token
/Identity/logout End session
/Identity/.well-known/openid-configuration OIDC discovery metadata

Frontend Architecture

graph TB
    subgraph App["TrackHub Web (React)"]
        router["React Router"]
        auth_mod["OAuth Module<br/><i>PKCE + Token Refresh Mutex</i>"]

        subgraph Pages["Pages"]
            dash["Dashboard<br/><i>Live Map + Positions</i>"]
            reports["Reports<br/><i>Preview + Excel/PDF Export</i>"]
            geofence["Geofence Manager<br/><i>Polygons + Circles</i>"]
            admin["Account Management<br/><i>Users, Units, Documents, Alerts</i>"]
            sysadmin["System Administration<br/><i>Accounts, Clients, Features</i>"]
            gps["GPS Integration<br/><i>Operators, Sync, Health</i>"]
            profile["Profile<br/><i>Settings</i>"]
        end

        help["Contextual Help<br/><i>Help button / F1, EN+ES topics</i>"]

        subgraph Services["API Services"]
            gql_client["GraphQL Client<br/><i>Manager, Security, Router,<br/>Geofencing, Telemetry</i>"]
            rest_client["REST Client<br/><i>Reporting, Documents</i>"]
        end
    end

    router --> Pages
    Pages --> Services
    Pages --- help
    auth_mod -->|Bearer Token| Services

    gql_client -->|"GraphQL over HTTPS"| backend["Backend Services"]
    rest_client -->|"REST over HTTPS"| reporting["Reporting API"]
Loading

Frontend Security Hardening

  • Token refresh mutex — Prevents race conditions during concurrent token refresh
  • GraphQL injection prevention — Values travel as GraphQL variables only (no string interpolation of user input)
  • Schema-checked operations — Every GraphQL document is validated by codegen against the producers' exported SDLs; drift is a compile error
  • Error Boundary — React component-level error handling
  • Notification system — MUI Snackbar replaces raw alert() calls
  • Security headersX-Content-Type-Options, X-Frame-Options
  • Request timeout — 30-second Axios timeout on all requests (60 s for file transfers)
  • Help content rendering — Markdown rendered with raw HTML disabled at both authoring (build validation) and rendering (skipHtml) layers

In-App Help & User Documentation

User documentation ships inside the portal build: one Markdown topic per screen/feature per language under TrackHub/public/help/{en,es}/, with YAML frontmatter mapping topics to route keys. A build-time tool (scripts/build-help.mjs) validates the content (language parity, screen coverage against routes.tsx in both directions, internal topic: links, no raw HTML) and generates a manifest.json consumed by the Help modal (Help button / F1 on every screen: contextual topic, browsable index, client-side search). See the User Guide page.


Backend Architecture (Clean Architecture + CQRS)

Each backend service follows Clean Architecture with four layers:

graph LR
    subgraph Layers["Dependency Direction →"]
        web["Web<br/><i>Endpoints</i>"]
        app["Application<br/><i>Commands / Queries</i>"]
        domain["Domain<br/><i>Entities / Interfaces</i>"]
        infra["Infrastructure<br/><i>DB / External APIs</i>"]
    end

    web --> app --> domain
    infra --> domain

    subgraph Pipeline["CQRS Mediator Pipeline"]
        direction TB
        b1["1. Logging"]
        b2["2. Validation<br/><i>FluentValidation</i>"]
        b3["3. Authorization<br/><i>[Authorize] attribute</i>"]
        b4["4. Caching<br/><i>[Caching] attribute</i>"]
        b5["5. Rate Limiting<br/><i>[RateLimiting] attribute</i>"]
        b6["6. Handler Execution"]

        b1 --> b2 --> b3 --> b4 --> b5 --> b6
    end
Loading

Inter-Service Communication

All service-to-service calls use GraphQL with:

  • Polly resilience — Retry (3×, exponential backoff) + circuit breaker
  • Bearer token propagation — User context forwarded on downstream calls
  • 30-second timeout — Configurable per client
  • Client credentials flow — For background services (SyncWorker)

References

Clone this wiki locally