docker build -t gate-one-token-broker services/token-broker
docker run -d --name token-broker \
--network internal \
--read-only --tmpfs /tmp:size=16m,mode=1777 \
--cap-drop ALL --security-opt no-new-privileges \
-e TOKEN_BROKER_SHARED_SECRET=... \
-e TOKEN_BROKER_ENCRYPTION_KEY="$(openssl rand -base64 32)" \
-e TOKEN_BROKER_PUBLIC_BASE_URL=https://login.example.com \
-e TOKEN_BROKER_RETURN_TO_HOSTS=.example.com \
-e GITHUB_CLIENT_SECRET=... \
-v token_broker_data:/data \
gate-one-token-brokerToken broker
The token broker is the optional companion service behind Delegated credentials. It holds a registry of OAuth 2.0 providers and an encrypted per-user token store, and hands Gate One the access token of one authenticated user for one provider.
It is deliberately not an identity provider and not a reverse proxy: Gate One keeps its own identity; the broker only obtains and stores the user’s token for an upstream service.
Pause, resume and erase
Disconnect is a pause, not a deletion. The credential stays stored and Connect resumes the same delegation; the provider is never contacted by pause or erase.
| Action | Effect | Provider called? |
|---|---|---|
Connect | starts the OAuth flow when nothing is stored, or resumes a paused delegation with the stored token (refresh if needed; OAuth if the token can no longer work) | on a new authorization only |
Disconnect | pauses the delegation: Gate One receives
| no |
Erase | deletes the stored credential and metadata locally | no |
Erase all my data | deletes every stored credential of the user | no |
Erase is local only on purpose: the same credential may be used by the user elsewhere. To remove the application authorization itself, the user revokes it at the provider, in the provider’s own settings. The broker never calls the provider on erase (there is no revocation endpoint in the registry).
Rows are listed even when their provider is disabled or removed from
providers.json: the user can still Erase them.
Where the code lives
The reference implementation is
services/token-broker/
in the Gate One source repository, and it ships as
services_<version>_src.tar.gz in each release. It is a small Node.js
service with its own Dockerfile; it is not part of the gate-one
binary.
Install it
The broker runs as a separate container, reachable only from the internal network:
No published port and no ingress route: the broker is on the internal network only. Gate One reaches it at
tokenBroker.url(defaulthttp://token-broker:4000).Browser routes (
/connections,/connect/,/oauth/) are published through the login host and served by Gate One: settokenBroker.browserHost; Gate One authenticates the user with the configured provider and injects the identity. Do not expose the broker itself.Only Gate One holds
TOKEN_BROKER_SHARED_SECRET(with the broker itself). auth-bridge no longer receives it: its legacy broker routes are inert (503). An ingress may route the login host to Gate One, but it must never proxy the browser routes directly to the broker: the broker would answer 401 without the secret.State: the encrypted SQLite store lives on the
/datavolume; the key is in the environment, so a backup needs both the volume and the key.Hardening: read-only root, dropped capabilities, non-root user.
Note | Environment variables (all optional unless marked):
|
Add a delegation service
A provider is a registry entry, not code. Edit providers.json:
{
"providers": {
"github": {
"enabled": true,
"display_name": "GitHub",
"type": "oauth2",
"authorization_endpoint": "https://github.com/login/oauth/authorize",
"token_endpoint": "https://github.com/login/oauth/access_token",
"client_id": "Iv23li...",
"client_secret_env": "GITHUB_CLIENT_SECRET",
"scopes": [],
"pkce": false,
"refresh": true,
"token_auth_method": "client_secret_post"
}
}
}| Field | Meaning |
|---|---|
| disabled providers are neither advertised nor require a secret |
|
|
| HTTPS (HTTP only on loopback) |
| provider client id |
| environment variable holding the secret (never inline) |
| requested scopes; |
| send S256 PKCE (default |
| allow the refresh-token grant (default |
|
|
| extra string parameters; protocol params cannot be overridden |
Adding Google, Slack or an internal OAuth service is a registry entry, not a code change. Only genuinely non-standard providers need an adapter.
Gate One references the provider name
(routes[].upstream.tokenBroker.provider) and never sees its secret.
Endpoints
| Method | Path | Purpose |
|---|---|---|
|
| liveness/readiness (no auth) |
|
| enabled provider names and connection status |
|
| browser page (HTML/JSON): states and actions |
|
| pause the delegation (no delete, no provider call) |
|
| resume a paused delegation (OAuth fallback) |
|
| delete the local credential (no provider call) |
|
| delete every credential of the user |
|
| start the authorization-code flow |
|
| exchange the code, store the tokens |
|
| fresh access token, or an explicit error |
|
| internal local deletion (works for a disabled provider) |
|
| redacted posture (shared secret) |
|
| operator API (separate admin secret) |
/token/{provider} answers 200 with the access token, 404
not_connected, 403 delegation_disabled (the user paused the
delegation; the credential is kept), 409 reauthorization_required
(refresh rejected; the row is deleted) or 502 upstream_unavailable —
each non-200 with a connect_url.
Link it to Gate One
Gate One declares the broker once and a provider per route:
"tokenBroker": {
"url": "http://token-broker:4000",
"secretEnv": "TOKEN_BROKER_SHARED_SECRET",
"browserHost": "login.example.com",
"links": {
"services": {
"label": "Services",
"url": "https://login.example.com/connections"
}
}
},
"routes": [{
"host": "api.example.com", "path": "/github/*", "backend": "https://api.github.com",
"auth": {}, "upstream": { "tokenBroker": { "provider": "github" } }
}]browserHost is the public host on which Gate One serves the browser
routes. It may be omitted when links.services.url is an absolute URL:
Gate One derives the host from it (here login.example.com). Set it
explicitly when the link is absent, relative, or served by another host.
If both are absent, Gate One does not serve the page; an ingress must
never proxy those paths directly to the broker.
See Delegated credentials for
the route details and the
tokenBroker reference.
Operator API
Set TOKEN_BROKER_ADMIN_SECRET and call the admin endpoints with
X-Broker-Admin-Secret from the internal network:
curl -H "X-Broker-Admin-Secret: $ADMIN" http://token-broker:4000/admin/users
curl -H "X-Broker-Admin-Secret: $ADMIN" \
http://token-broker:4000/admin/users/<sub>
curl -X POST -H "X-Broker-Admin-Secret: $ADMIN" \
http://token-broker:4000/admin/users/<sub>/github/pause
curl -X DELETE -H "X-Broker-Admin-Secret: $ADMIN" \
http://token-broker:4000/admin/users/<sub>The operator can list users and connections, pause/resume a delegation, and delete credentials, but cannot add or replace a credential: no write endpoint exists. Responses never contain a token value. Every admin action is audited in the broker log.
The operator API is disabled when the secret is absent. The broker must stay on the internal network: the admin secret is the only credential.
The shared secret is a bearer secret: it proves that the caller
knows it, not who the caller is. That is why only Gate One (and the
broker) may hold it; the infrastructure check refuses a configuration
where another service (auth-bridge included) receives
TOKEN_BROKER_SHARED_SECRET. The browser routes are always served by
Gate One, never by a second proxy pointing at the broker.
Security posture
GET /security (shared secret) returns a redacted view for
gate-one doctor and the infrastructure checks: public base URL,
returnTo hosts, encryption enabled, admin API enabled, retention,
database permissions, enabled providers (HTTPS, PKCE, refresh, auth
method), token/user/paused/orphan counts. No secret and no token value is
returned.
The broker never validates the quality of the shared secret or of the provider secrets: that belongs to the deployment/provider. It does enforce the functional gates: secret present, AES key = 32 bytes, HTTPS endpoints, strict provider fields, local file permissions.
Licence
The broker is part of the Gate One distribution and follows the repository licence; it is not an authentication adapter. See Source and licence.