An external API usually gives you one credential: a client ID and secret, or a token. As soon as several people
or services need that API, the credential gets copied into each of them. Rotating it means finding every copy.
Removing one person’s access means rotating it for everyone.
The idea
Separate two things that usually travel together: holding a credential and being allowed to use it. Gatekeeper
holds the provider credentials. Users hold only a Gatekeeper session, and an admin decides which providers each
user may call. The consuming app never sees the provider’s secret.
You can try it in the live demo: it signs you in as a worker with one
sandbox provider. Call it and you get its data; ask for any other provider and Gatekeeper answers 403. The
sandbox's token never reaches the page.
How a call flows
The user signs in to Gatekeeper and gets a session token. The session is tracked in Redis.
The app sends Gatekeeper the provider name, method, path and body of the call it wants to make.
Gatekeeper verifies the session, loads the user from the database and checks that this provider is on their
list. If it is not, the answer is 403.
It loads the provider record and decrypts its secret, stored with AES-256-GCM, at the moment of use.
It authenticates the way that provider expects (OAuth client credentials, bearer token or basic auth),
forwards the call and returns the response.
Permissions are read from the database on every call, not baked into the token. Revoking a provider takes effect
on the next request, not when the token expires.
Administration
Admins create and remove users, switch roles between admin and worker, and grant or revoke provider access per
user. Every admin operation goes through a rate limiter, a session check, a role check and a TOTP code.
Because sessions live in Redis and not only inside the token, signing out invalidates all of a user’s active
sessions at once.
The three flows as I drew them when designing Gatekeeper: onboarding, administration and the proxied call.
What it is built on
TypeScript and Express, MongoDB for users and providers, Redis for sessions. Encryption, sessions, the HTTP
client and database access come from tslib, my shared TypeScript library, so the same pieces are reused across
my other services.
The web console is a small SvelteKit app used as a backend for frontend: the browser never talks to the API,
the session token lives in an httpOnly cookie, and provider secrets are write-only in the admin screens. It runs
on the same host as the API so rate limits still count each visitor, not the console.
Trade-offs
Every proxied call now depends on Gatekeeper: if it is down, those integrations are down too, and it adds one
network hop. Centralizing secrets is the point, and it also makes the Gatekeeper server the one machine that has
to be protected best.
It is deliberately small. It authenticates, authorizes and forwards; it is not an API management platform.