Admin App — Hosting & Access Control Plan
Status: Decided in principle, 2026-08-22. Not built yet — the admin app itself is still a named-but-unbuilt piece of the Keystone spike scope (traxsidentity-spike-scope.md Section 1). This is the plan to build against once it's underway, not a record of something already running.
1. The core decision: the admin app is infrastructure, not a product
RoundTrip Web and Waypoint Web are both Cloudflare Pages — public, customer-facing, meant to be reached by anyone with a login. The admin app is different in kind: it's a single-operator internal tool for managing the whole platform, with no customers ever touching it. Hosting and protecting it the same way as a public product would be treating two genuinely different things as one. This plan deliberately breaks from the Cloudflare Pages pattern for that reason — not an oversight, a decision.
2. Hosting: Azure App Service
The admin app (React + TypeScript) gets deployed to an Azure App Service, the same way RoundTrip API and Waypoint API already are — same portal, same ADO pipeline pattern (traxs-self-hosted agent pool), same Key Vault secret-reference convention. No new hosting platform to learn for a tool only one person operates.
Why App Service over Azure Static Web Apps (the more literal Cloudflare Pages equivalent): App Service has a native, simple Access Restrictions blade — plain IP allowlisting, built into the same resource type already in daily use. Static Web Apps does not have this natively; restricting it by IP requires putting Azure Front Door or Application Gateway in front of it, which is real added infrastructure for a one-user tool. App Service gets the same outcome with less to stand up and maintain.
3. Authentication: Keystone
The admin app authenticates through Keystone (formerly "TraxsIdentity") itself — the one account that matters most, acting as a real early production test of the identity service while it's still being trusted with everything else. See traxsidentity-framing.md Section 9 and traxsidentity-spike-scope.md Section 1.
4. Network-layer gating: Tailscale, added at the production-tier line
Auth alone (even correct auth) means "only someone with valid credentials gets in." Network gating means "only someone on an approved device can even reach the login page to try." These are two independent layers, and the admin app is worth the second one specifically because it's the single highest-value target on the whole platform — full visibility and control across every product at once.
Not needed yet. Following the same two-tier framework already set for Keystone itself (traxsidentity-framing.md Section 8): network gating for the admin app is a production-tier concern, the same bucket as MFA, audit logging, and uptime monitoring — not required before anything real depends on it, worth having in place before it is.
When it's added, it's Tailscale, not a traditional VPN and not Cloudflare Access. A traditional VPN's daily "remember to connect" step is real friction that erodes discipline over time. Cloudflare Access was tried and disliked directly (its Zero Trust product UX, separate from Cloudflare Pages itself). Tailscale is a mesh VPN that runs as a persistent background service — installed once, effectively always connected, no daily ritual — free for personal use up to 6 users, far more than one operator needs. Azure App Service's Access Restrictions blade would then simply allowlist the Tailscale-assigned IP range instead of a public one.
5. Options considered and set aside, with reasons (worth keeping so this doesn't get re-litigated later)
- A native Mac/Windows app instead of a hosted React app — considered 2026-08-22. Not meaningfully more secure by default; the real lever is network exposure, not frontend technology, and a native app can be built to be less secure than a well-built web app if credentials aren't handled correctly. The real cost is operational: two signed, notarized, distributed binaries with their own update pipeline, a different tech stack from every other Traxs frontend, all landing on one solo developer — the same shape of mistake that sank Keycloak. Set aside. (Tauri — a lightweight native shell wrapping the existing React code, giving OS-keychain token storage without a full rewrite — is a real middle-ground option if credential storage specifically becomes a concern later, not ruled out permanently, just not the starting point.)
- Cloudflare Access — technically capable, disliked in practice; its ops-manual footprint and Zero Trust product UX were called out directly as annoying. Set aside in favor of Tailscale, which solves the same problem with less day-to-day friction.
- A traditional VPN — the "remember to connect first" step was named directly as a real, honest concern, not dismissed. Tailscale addresses this specifically by not requiring a manual connect step at all.
- Relying on Keystone auth alone, indefinitely — not wrong for right now (nothing live depends on Keystone yet), but not the end state either, given the admin app's outsized blast radius if credentials are ever phished or leaked. Revisit at the production-tier line, not before.
6. Open items / when to revisit
- This plan assumes the admin app gets built after the Keystone spike proves the core mechanics (
traxsidentity-spike-scope.md). Nothing here needs deciding again before then. - Azure App Service Access Restrictions + Tailscale should be wired up as part of the admin app's own move from "ready to use" to "production tier" — not before, not forgotten after. Worth adding as an explicit checklist item wherever the admin app's own build gets scoped for real.