-
Notifications
You must be signed in to change notification settings - Fork 1
Technology
TrackHub is built on .NET 10 and React 19 + TypeScript. Backend services expose GraphQL (via HotChocolate) with REST Minimal APIs where file transfer or anonymous access is required. Authentication is the Authorization Code Flow with PKCE via OpenIddict, for both browser and mobile clients.
Related pages: Architecture · Security and Identity · Frontend
graph TB
subgraph Frontend["Frontend Layer"]
react["React 19 + TypeScript 7"]
vite["Vite 8 + Vitest 4"]
mui["Material UI (MUI)"]
maps["Leaflet / Google Maps"]
tanstack["TanStack Query"]
codegen["graphql-codegen<br/><i>typed 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 16 (GraphQL)"]
minapi["Minimal APIs (REST)"]
cqrs["Custom CQRS Mediator"]
fv["FluentValidation"]
end
subgraph Data["Data Layer"]
pg["PostgreSQL 14+"]
postgis["PostGIS Extension"]
ef["EF Core 10"]
end
subgraph Infra["Infrastructure"]
docker["Docker + Compose"]
nginx["Nginx Reverse Proxy"]
polly["Polly (Resilience)"]
end
react --> tanstack --> codegen --> hc
react --> pkce --> oiddict
oiddict --> jwt --> hc & minapi
hc --> cqrs --> ef --> pg
minapi --> cqrs
pg --- postgis
| Component | Version | Notes |
|---|---|---|
| .NET / ASP.NET Core / EF Core | 10.0 | Across every repository, including tools/Tools.McpExtractor
|
| HotChocolate | 16.5 | Every GraphQL service |
| PostgreSQL | 14+ | PostGIS required for the geofencing and trip schemas |
| React | 19 | |
| TypeScript | 7 |
strict; tsc is the TypeScript lint gate |
| Vite / Vitest | 8 / 4 | Rolldown-based Vite; react-scripts/CRA has been removed |
The project convention is prefer latest: a newly introduced component is never pinned back. If a tool lags (as typescript-eslint currently does for TypeScript 7), scope or disable that tool rather than downgrading the stack. When bumping the shared stack, remember that Tools.McpExtractor pins EF Core directly and must move with it.
TrackHub uses the Authorization Code Flow with PKCE for all public clients (web, mobile and driver mobile). This removes the need to store a client secret in the browser while keeping the exchange secure 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 }
| Client | Type | Grants | Scope | Usage |
|---|---|---|---|---|
web_client |
Public | authorization_code, refresh_token | web_scope |
React web portal |
mobile_client |
Public | authorization_code, refresh_token | mobile_scope |
Mobile application |
driver_mobile_client |
Public | authorization_code, refresh_token | driver_mobile_scope |
Driver mobile application |
postman_client |
Public | authorization_code, refresh_token | web_scope |
API exploration during development |
router_client |
Confidential | client_credentials | service_scope |
Router service identity |
syncworker_client |
Confidential | client_credentials | service_scope |
SyncWorker service identity |
security_client |
Confidential | client_credentials | service_scope |
Security service identity (audit forwarding) |
geofence_client |
Confidential | client_credentials | service_scope |
Geofencing service identity (alerts, job runs) |
trip_client |
Confidential | client_credentials | service_scope |
TripManagement service identity |
All four scopes resolve to the same resource — the audience trackhub_api, which every backend service validates.
Registering a confidential client is only half the work: a service identity also needs rows in security.service_client_permissions. See Security and Identity.
| Endpoint | Purpose |
|---|---|
/Identity/authorize |
Start the authorization flow |
/Identity/token |
Exchange a code for tokens, refresh tokens, or perform a client-credentials grant |
/Identity/revoke |
Revoke a token |
/Identity/logout |
End the session |
/Identity/.well-known/openid-configuration |
OIDC discovery metadata |
Default lifetimes are OpenIddict's: access token 1 hour, refresh token 14 days, authorization code 5 minutes.
| Style | Where | Why |
|---|---|---|
| GraphQL | Manager, Security, Router, Telemetry, Geofencing, TripManagement | The default for both portal and service-to-service traffic |
| REST (Minimal APIs) | Reporting (all endpoints); Manager (documents, announcements); Router; TripManagement (public tracking) | File upload/download, and anonymous endpoints that must bypass the mediator pipeline |
| REST (MVC) | AuthorityServer | The login UI and OAuth endpoints |
GraphQL hardening — max execution depth 15, exception details only in Development — is applied by AddTrackHubGraphQLServer and therefore covers every GraphQL service uniformly.
The React portal's internals — data-access layering, code generation against the producers' SDLs, i18n, contextual help and the public status page — are documented on the Frontend page.
Security hardening applied in the browser:
- Token refresh mutex — a single in-flight refresh, so concurrent 401s cannot race
- GraphQL injection prevention — values travel as GraphQL variables only; there is no string interpolation of user input into documents
- Schema-checked operations — every document is validated by codegen against the producers' exported SDLs, so backend drift is a compile error
-
Error boundary and a toast notification system (no raw
alert()) -
Security meta headers —
X-Content-Type-Options,X-Frame-Options - Request timeouts — 30 s for API calls, 60 s for file transfers
-
Help content rendering — Markdown is rendered with raw HTML disabled at both the authoring layer (build validation) and the rendering layer (
skipHtml) -
Map popup escaping — Leaflet and Google InfoWindow assign their content via
innerHTML, so every interpolated value passes throughescapeHtml
The four-layer structure, the CQRS mediator and its behavior pipeline, the composition root and the middleware order are documented on the Architecture page. Service-to-service calls, resilience policies and identity propagation are on Inter-Service Communication.
Platform
- Architecture
- Technology
- Inter-Service Communication
- Database
- Security and Identity
- User Permissions Overview
Services
- Manager
- Telemetry
- Router
- Adding a Provider
- Geofencing
- Trip Management
- Reporting
- Common Library
- Frontend
Engineering
User docs
- User Guide (ships in the app)