FGA as an API Gateway Enforcement Point
A real Cloudflare Worker gateway, a real OpenFGA store — the access token carries no permissions claim at all; the allow/deny decision happens entirely at the gateway.
tokens app
session, no RBAC scopes on this token
Access Token
forwarded server-side, as a Bearer header
Real gateway
Cloudflare Worker
PEP — verifies JWT (RS256, exact aud/iss)
Real FGA store
OpenFGA Check
PDP — can_read / can_write, ReBAC tuples
Allow / Deny
real document response, or a real 403 / 503
Not a token-hiding boundary
The proxy route below forwards your access token to the Worker server-side, but this page also shows you that same token via the standard token viewer — you can see exactly what the Worker receives. The server-side hop exists to control how the token reaches the Worker (a known audience, a controlled forward), not to hide it from you.
This is the actual OpenFGA authorization model deployed to the real store the Worker checks against — not a simplified stand-in. viewer propagates through team membership via the member from team indirect-relationship pattern; can_read/can_write are the only two relations the gateway ever checks, never viewer/editor directly.
model
schema 1.1
type user
type team
relations
define member: [user]
type document
relations
define owner: [user]
define team: [team]
define viewer: [user] or owner or member from team
define editor: [user] or owner
define can_read: viewer or editor
define can_write: editorAnd the seed tuples that produce alice/bob/carol's three different outcomes (user:alice stands in for the real auth0|... subject in the live store):
| user | relation | object |
|---|---|---|
| user:alice | member | team:platform-eng |
| user:bob | member | team:platform-eng |
| user:alice | owner | document:doc-001 |
| team:platform-eng | team | document:doc-002 |
| user:alice | editor | document:doc-002 |
Carol has no row in this table at all — that absence is the entire reason she's denied everywhere, not a separate deny rule.
This demo needs a specific, pre-built OpenFGA tuple graph to show the contrast between the three identities below, so it isn't self-serve for a brand-new account. Sign in with one of three shared test accounts:
fga-gateway-alice@atko.email
owner of doc-001, editor of doc-002 (team + direct) — allow on everything
fga-gateway-bob@atko.email
viewer-only on doc-002 via team membership, no relation to doc-001 at all — read allowed on doc-002, everything else denied
fga-gateway-carol@atko.email
no OpenFGA tuples at all — denied on every document, every action
Password for each account was set during this demo's provisioning and is not repeated here — ask whoever set up this tenant if you don't have it.
Log in to try it →Switching identities? Sign out first, then use this same link again — it forces a fresh credential prompt (prompt=login) rather than silently re-using whichever Auth0 SSO session is still active in this browser.
Strengths
- A centralized policy enforcement point decouples authorization logic from application code — the Worker, not any individual backend route, is the single place that decides allow/deny, so the same check logic is enforceable at any gateway in front of any backend.
- Relationship-based access control (ReBAC) via OpenFGA scales past flat RBAC — a single tuple change (e.g. team membership) can flip access to many resources at once, without touching application code or redeploying anything.
- The gateway fails closed, not open: an OpenFGA outage or timeout returns 503 (unavailable), never a false allow or a false deny — the demo deliberately distinguishes "denied" from "the authorization service itself is down," which most hand-rolled integrations don't bother to separate.
Risks & Considerations
- Every protected request now has a real network dependency on OpenFGA, on top of the gateway itself — a slow or unreachable FGA store adds latency (or, correctly, a 503) to every single request, not just a rare edge case.
- A validly-audienced access token can call the Cloudflare Worker directly, bypassing this tokens app entirely — the audience check proves the token is for this API, not that it was obtained through this specific application's login flow. This is a stated, accepted residual risk for a demo, not something this architecture eliminates on its own.
- The OpenFGA store and its authorization model are a single point of trust for every downstream decision — if the model is misconfigured, or the pinned model ID is ever repointed carelessly, every protected route behind the gateway is affected at once.
Hardening Checklist
- Fail closed on infrastructure errors rather than fail open — already built this way here (503 on FGA outage/timeout, never silently treated as an allow or a deny).
- Pin the authorization model ID explicitly on every check — already done here — rather than letting checks silently resolve against whatever model is "latest" in the store.
- Rate-limit the check path in production; a single compromised or leaked token could otherwise generate unbounded authorization-check traffic.
- For a real production deployment (not this demo), consider mTLS or a signed-request scheme between the gateway and the actual backend it fronts, so the backend itself can verify a request genuinely passed through the enforcement point rather than trusting network topology alone.