Skip to main content

Reverse Proxy

AuthProxy includes a custom reverse proxy built as ASP.NET Core middleware. It forwards requests to backend services based on database routing configuration with per-request authentication, rate limiting, and header injection.

Implementation

The proxy pipeline processes requests through middleware in strict order:

ApiPathMiddleware → CorsMiddleware → ResponseCaching → FileCacheMiddleware → ReverseProxyMiddleware

Key implementation details:

  • HttpClient per backend with MaxConnectionsPerServer = 256 and connection pooling
  • Streaming: HttpCompletionOption.ResponseHeadersRead — response body streams without buffering
  • Selective header forwarding: AuthProxy forwards a small allowlist of incoming headers and injects trusted internal context headers
  • Lock-free session lookup: ConcurrentDictionary for O(1) session resolution

Proxied Headers

AuthProxy is not a transparent header passthrough. The reverse proxy forwards only selected incoming headers and then injects its own trusted headers after route, auth, and security checks.

Incoming headers passed through

HeaderWhen forwardedWhy backend needs it
X-Device-GuidPresent on the incoming requestPreserves device binding for flows such as anonymous chat, capability mode, and other device-scoped module logic
X-WalletIdPresent on the incoming requestWallet context for multi-wallet TrexWallet flows
X-SignaturePresent on the incoming requestSecond-factor signature (validated by AuthProxy, surfaced to backend as X-SignResult: valid)

Antiforgery (RequestVerificationToken) is intentionally NOT forwarded. Admin Razor Pages in every backing module disable antiforgery globally (AddRazorPages(... ConfigureFilter(new IgnoreAntiforgeryTokenAttribute()) ...)). CSRF protection lives at the AuthProxy layer: SameSite session cookie + RouteFlags.ScopeCheck on route_map. Per-action authorization is enforced by Request.CheckScope(...) in each handler. Browser cookies (including .AspNetCore.Antiforgery.*) are not propagated to backing modules at all, so antiforgery validation could not succeed even if it were enabled.

Headers injected by AuthProxy

HeaderConditionWhy backend needs it
X-ProjectAlwaysProject context for module-side routing and multi-project logic
X-CrmSession-authenticated requestAuthenticated user's CRM ID
X-ScopesSession-authenticated requestUser scopes for module-side authorization
X-KeyTypeSession-authenticated requestLogin/auth method context as the numeric LoginMethod value (e.g. 1 PassKey, 2 Fido2Key, 4 UserKey, 8 AppLogin) — ((int)session.login_type), not a name
X-AppIdValidated app_id or AppLogin sessionTrusted application context after ownership validation
X-MCP-App-IdMCP App sessionSeparate app context for module-side MCP restrictions
X-Pkey-CrmPublic key auth (CheckPubKey)CRM ID of the validated public key owner
X-CountryRoute has GeoEnrich flagISO country code derived from client IP (best-effort)
X-SignResultIncoming x-signature was validated by AuthProxyLets backend trust that second-factor signature verification already succeeded

Database-Driven Routing

Routes are configured centrally and exposed through the admin panel. Operators do not edit routing tables directly; they manage path matching, target service, and behavior flags through the UI.

Route Fields

  • id: Unique route identifier
  • path: Route prefix to match (e.g., /trex, /wallet); the longest matching prefix wins
  • location: Target HTML file for SPA routes (e.g., /wallet.html), or path rewrite for proxy
  • address: Backend service URL (e.g., http://trexwallet:80). Empty = SPA/static route
  • flags: Route behavior flags (see below)
  • tag: Human-readable description
  • ip_white_list: Number of the IP allowlist configured in Settings; -1 disables the route-level IP check

Route Flags

FlagValueDescription
NoRateLimit1Skip per-session rate limiting
AllReferers2Allow any Referer header
AppAuthAndCORS4Route is CORS-eligible from external app domains; works only paired with AppFlag.AppAuthAndCORS on the calling user_app (see CORS gating below)
ScopeCheck8Require route.path to be present in session user.scopes
NoAuth16Allow unauthenticated access
NoLoginForm32Don't redirect to login page
CheckAppId64Validate app_id query parameter (session or pkey) and inject X-AppId
CheckPubKey256Allow public key authentication (header pkey); inject X-Pkey-Crm
GeoEnrich512Add X-Country header from IP geolocation
BuiltIn1024Built-in AuthProxy controller (not proxied)
InternalApi2048Inter-module API: private IP allowlist + X-API-Key
Dav4096WebDAV/CardDAV route: authenticates a CardDAV key via HTTP Basic and passes WebDAV request headers through

Flags combine with bitwise OR: NoAuth + GeoEnrich = 528. Common combinations:

  • 528 = NoAuth + GeoEnrich — public anonymous endpoint with country enrichment (e.g. /payment/v1/*).
  • 848 = NoAuth + CheckAppId + CheckPubKey + GeoEnrich — merchant H2H endpoints (/payorders/v1/*).
  • 8 = ScopeCheck — admin-style endpoint, requires route.path to be in session user.scopes.

Bit 128 is unused. IP restriction is configured through ip_white_list, not a route flag. If the referenced list is missing or empty, the route denies every source IP.

CORS gating: app × route flag pair

CORS for cross-origin calls from external applications is gated by two flags simultaneously (added in 2026-04):

  1. The calling user_app row in the user_app table must have AppFlag.AppAuthAndCORS set.
  2. The matching route_map row must have RouteFlags.AppAuthAndCORS set.

If either side is missing, the preflight returns 403. This pair-gate prevents an app with a verified domain from probing arbitrary internal routes that were never marked as cross-app-friendly.

+---------------------------+        +---------------------------+
| user_app.flags | | route_map.flags |
| AppAuthAndCORS = ON | AND | AppAuthAndCORS = ON | -> CORS allowed
+---------------------------+ +---------------------------+

Either side OFF -> preflight 403

Access-Control-Allow-Origin reflects the calling origin from the verified user_app domain list; cross-app preflight responses include Vary: Origin and Access-Control-Allow-Credentials: true. Allowed headers are limited to Content-Type, X-Device-Guid, X-WalletId.

Multi-node deployment (pN / proxyNode)

A project can run several AuthProxy nodes behind one common domain. Each node has a ServiceId (10–19; node p1 = 10) and is reachable at the subdomain pN.<domain> (p1.app.com, p2.app.com), while the bare <domain> is the shared entry that steers the browser to a node.

  • The application domain (<domain>) is distinct from the node subdomain (pN.<domain>). API responses expose the node separately as proxyNode (get_info, sessions, login_log); they do not encode it into domain.
  • Session cookies are issued on the apex domain and are valid across all pN.<domain> subdomains. CORS / Referer validation (IsHostAllowed / IsOriginAllowed) accepts these subdomains.
  • Cross-application access redirects preserve the serving node by carrying the current proxy_id (redirect query) / proxyId (popup postMessage), so the flow returns to the same node.

Request Processing Flow

  1. API Path Middleware processes incoming requests
  2. Route Lookup finds matching route in database by path pattern
  3. Authentication Check validates user session and scopes
  4. Request Forwarding proxies request to backend service
  5. Response Processing returns backend response to client

Request Mirroring

AuthProxy can duplicate an incoming request to one or more extra hosts in parallel with the primary reverse proxy. This is used for failover (two receiving hosts) and for traffic monitoring/auditing.

How it works:

  • Matches by route.path (the same first-URL-segment key as the primary route).
  • The copy is dispatched in parallel with the primary request (fire-and-forget), before the backend response is awaited. The mirror response is ignored and never affects what the client receives.
  • The full request is mirrored: method, rewritten URL (after location rewrite), headers (including injected X-* and X-API-Key), and body.
  • For mirrored paths the request body is buffered in memory so it can be sent to both the primary and the mirror(s). All other routes keep the regular zero-copy stream, so their latency is unchanged.
  • Mirror requests use a separate cancellation token (not the client's RequestAborted), so a client disconnect does not abort the mirror.

Configuration

Mirroring is a dynamic setting (SettingType.RequestMirror), managed from the admin panel — no schema change. The value is a flat JSON object { "path": "address" } (one path → one address, no nested arrays or sub-objects):

{ "/trex": "http://trex-standby:80", "/crm": "http://crm-standby:80" }
  • The key is the route path to mirror; the value is the mirror target address.
  • One path → one address. To mirror a single path to several hosts (failover to N hosts), add another RequestMirror row (different setting_index/node_num) with the same path — entries with the same path across rows are merged (addresses combined). Keys cannot repeat within one JSON object.

The configuration is reloaded on the standard settings cycle (~100s); mirror HTTP clients are cached per address.

Disabling / removing a mirror

Removing (or clearing) the RequestMirror setting reverts the route to the normal flow. On the next reload (≤100s, or immediately on restart) the mirror table is rebuilt empty, so the route falls back to the regular zero-copy proxy path with no duplication. Cached mirror HTTP clients are not actively evicted (same as primary route clients), but they go idle — no traffic is sent to them and their pooled TCP connections expire (5 min lifetime); the objects are released on process restart.

Module Integration

AuthProxy integrates with other ItBuild modules through configured endpoints:

Each module route points to an internal service URL inside the project deployment network. Operators configure those URLs per environment; public clients never see the internal addresses.

Admin Interface

The routing configuration can be managed through the admin panel. Use it to review route prefixes, authentication flags, module targets, and CORS behavior without editing deployment files manually.

Security Features

  • Scope-based Access Control: Routes can require specific user scopes
  • Authentication Integration: Automatic session validation
  • Database-driven Configuration: Dynamic routing without restarts
  • Longest-prefix Routing: The most specific matching route processes the request

Static File Serving

In addition to API proxying, AuthProxy can serve static files for SPA applications by configuring routes with file locations instead of backend addresses.