React SPA + BFF — Golden Path (Step‑by‑Step)
What is the golden path?
The golden path is the recommended, production‑grade way for SPAs to reach backend APIs through the BFF. It
a) keeps tokens out of the browser, b) uses same‑origin /api/** for a simple DX, and c) lets Traefik/BFF authorize
every request. Follow this path unless you have a strong reason not to.
Prereqs
- BFF running with
routes.yamlloaded - IdP reachable by the BFF
- Dev: CORS allowlist includes your Vite origin; Prod: same‑origin routing via Traefik
- Hosts entries for local TLS domains if you use the compose stack (see QA guide)
Steps
-
Configure env
VITE_BFF_BASE_URL=https://bff.example.com/api(or/apiwhen same origin)
-
Wire the app
- Wrap your app with
AuthProviderand callsetBaseUrl. Place this insrc/main.tsx.
- Wrap your app with
import { AuthProvider, setBaseUrl } from '@empowernow/bff-auth-react'
setBaseUrl(import.meta.env.VITE_BFF_BASE_URL || '/api')
-
Protect routes
- Use an
AuthGuardto redirect anonymous users to/loginand preservereturnUrl.
- Use an
-
Call your APIs
- Use
apiClient/fetchWithAuthto call canonical/api/<app>/...paths. Do not attach Authorization headers; the cookie is sent automatically.
- Use
-
Define routes in the BFF
- Add entries under
routes:referencing your backend service. Use the canonical/api/<app>/*shape and setauth: session.
- Add entries under
services:
my_service:
base_url: "http://my-service:8080"
timeout: 30
routes:
- id: "my-app-items"
path: "/api/myapp/items/*"
target_service: "my_service"
upstream_path: "/items/{path}"
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
auth: "session"
- Test the flow
Mental model: /api is the BFF
- Calls that look “local” like
/api/<app>/...always go to the BFF first. - The BFF uses
routes.yamlto translate them into real backend requests and enforces session/CSRF/PDP.
Concrete example
GET /api/myapp/items/123 → BFF → GET http://my-service:8080/items/123
Dev vs Prod base URL
- Dev (separate origins): set
VITE_BFF_BASE_URLto the BFF origin (for examplehttp://localhost:8000/api) and ensure CORS allows your UI origin. - Prod (same origin): serve the SPA via Traefik with the BFF and set
VITE_BFF_BASE_URLto/api.
Common pitfalls
- Double
/apiprefix (client path should start with/api/..., wrapper strips leading/apionce) - Missing dev origin in CORS allowlist → opaque 401
- Wrong
VITE_BFF_BASE_URLin dev → calls go to the wrong origin
Checklist
-
AuthProviderwraps<App /> -
VITE_BFF_BASE_URLset - Calls go to
/api/...(no direct service URLs) - Route exists in
routes.yaml - PDP mapping exists in
pdp.yamlfor your route; action allowed (403 otherwise) - Unauthenticated call returns JSON + CORS headers in dev (verifies error‑path CORS)
Automation Studio (Visual Designer) checklist
- SPA calls same-origin endpoints only
- CRUD + SSE:
/api/crud/... - PDP (AuthZEN):
/access/v1/evaluationand/access/v1/evaluations(preserved path)
- CRUD + SSE:
-
ServiceConfigs/BFF/config/routes.yamlentries exist- CRUD entries target
crud_service - PDP entries target
pdp_servicewithpreserve_path: true
- CRUD entries target
- Credentials included from the browser
- fetch:
credentials: 'include' - axios:
{ withCredentials: true } - SSE: same-origin works by default; cross-origin dev uses
{ withCredentials: true }
- fetch:
- Dev vs Prod base URL
- Dev:
VITE_BFF_BASE_URL= BFF origin (CORS allows UI origin) - Prod:
VITE_BFF_BASE_URL=/api
- Dev: