Gateway Overview
The PBAC Gateway is a Policy Enforcement Point (PEP) that sits between MCP clients (Claude Code, AI agents) and backend services. It enforces authorization on every tool call by delegating to the PBAC Authorization Server.
The gateway has no policy logic. It calls the AS /introspect endpoint, gets back yes/no plus obligations, and enforces the result.
MCP client (Claude Code)
|
| POST /mcp (Bearer token, JSON-RPC 2.0)
v
+------------------+
| MCP Gateway | 1. Validate token via /introspect
| (PEP) | 2. Enforce scopes + obligations
+--------+---------+ 3. Route to protected resource
|
+----+----+
| |
v v
PBAC AS Your backend APIs
(PDP) (Drive, Jira, Slack, etc.)
What a gateway protects
A single gateway fronts one or more backends — your own APIs, SaaS providers, databases — and exposes their operations as MCP tools, each with a required scope.
| Concept | What it is |
|---|---|
| Backend | An HTTP API the gateway sits in front of (e.g. your GitHub, your internal Postgres, a SaaS app) |
| Tool | A named MCP operation on that backend (e.g. github_list_issues, db_query) |
| Scope | The OAuth scope the token must carry for the gateway to forward the call (e.g. github:issues:read, db:read) |
You define backends, tools, and scopes through the dashboard's Gateway tab.
Request flow
Every tool call follows the same path — this is what "policy on every call" means in practice:
- The MCP client sends
POST /mcpwith a bearer token. - The gateway looks up the scope required by the requested tool.
- The gateway calls the AS
/introspectwith the token, the required scope, and the resource context. - OPA evaluates policy and returns
active: trueoractive: falseplus any obligations. - If authorized, the gateway forwards the call to the backend.
- The backend's response is returned to the client.
Every step is logged to the instance's audit trail: user, agent, tool, scope, decision, timestamp.
Security model
- Fail-closed everywhere: missing token → 401; invalid/inactive token → 403; AS unreachable → 403; unknown obligation → 403
- Per-route scope enforcement: each tool and resource declares its required scope
- Resource context: resource type and ID passed to introspection for fine-grained policies
- Protocol in context: introspection includes
"protocol": "mcp"or"protocol": "http"for OPA - WWW-Authenticate headers: RFC 6750 with
resource_metadataURL for MCP client step-up flows
Fail-closed details
If the AS is unreachable or returns an error during introspection, the request is denied. Every path leads to 403 — never "allow on error." This prevents authorization bypass during an AS outage.
| Condition | Result |
|---|---|
| Connection error or timeout fetching the gateway's RS token | Deny (403) |
Connection error or timeout calling /introspect | Deny (403) |
Non-200 from /introspect | Deny (403) |
active: false in the introspection response | Deny (403) |
Health and diagnostics
Health endpoint — the gateway exposes a cheap, no-auth health probe via its protected-resource metadata:
curl -sf https://<your-gateway>.policyarc.com/.well-known/oauth-protected-resource
HTTP 200 means the gateway is up and its AS is reachable. The dashboard runs this check automatically and surfaces the result on the Gateway tab.
Dashboard signals — the Instance page surfaces:
- Gateway health tile (green / yellow / red)
- Per-call audit log with scope, decision, timestamp
- Live logs for the gateway container
For troubleshooting specific failure modes, contact us.
Per-user upstream credentials (user_secret)
Some upstreams reject IdP-issued bearer tokens and accept only a credential the
user holds directly — e.g. Jenkins (a username + API token over HTTP Basic). For
these, the user_secret upstream-auth mode lets each user link their own
credential once so the upstream still attributes actions to the real user.
A connector declares the fields to collect and how to turn them into an auth header:
upstream_auth:
type: user_secret
fields:
- { name: username, label: "Jenkins user", type: text, required: true,
help: "Shown top-right next to your avatar." }
- { name: api_token, label: "API token", type: password, required: true,
help: "Create at Jenkins → your name → Security → API Token." }
headers:
Authorization: "Basic {{basic field.username field.api_token}}"
- Collection — the first tool call with no stored credential returns an MCP
URL elicitation pointing at a PolicyArc-hosted page (
/gateway/connect). The user enters the credential in their browser; it posts only to the AS and never passes through the MCP client or the model. It is stored encrypted per(subject, resource)and the tool call auto-retries. - Header template —
{{field.<name>}}substitutes a collected value;{{basic <a> <b>}}emitsbase64(a:b)for HTTP Basic. The optional per-fieldhelpstring is shown beneath the input on the collection form. - Rotation — if the upstream later returns
401, the gateway re-prompts the user to reconnect (overwriting the stored credential). A403is a genuine authorization answer and is relayed unchanged. - Disconnect — an operator can remove a user's stored credential via
DELETE /admin/api/managed-resources/{name}/user-secrets/{subjectId}. PolicyArc only forgets its stored copy; revoke the token at the upstream itself to fully invalidate it.
Keeping a resource's tool list current
A managed resource's routes — and therefore the MCP tools it exposes — are a snapshot of its connector taken when the resource was created. Installing a newer connector rewrites that connector's policy, scopes and resource type, but never an existing resource's routes. So a resource created before its connector gained a tool does not expose that tool, at any connector version.
The resource detail page (Resources → Managed → name) shows which connector and version its routes came from, and offers Update tools when the build bundles a different version:
Connector identos.jenkins tools from v1.2.1 [v1.3.0 available] Update tools
Pressing it re-renders routes from the connector — installing the connector
first if this instance lags, so the new routes' scopes exist rather than 403ing.
The same action is available to automation:
curl -X POST -H "X-Admin-API-Key: $PBAC_ADMIN_API_KEY" \
http://localhost:8080/admin/api/managed-resources/jenkins/update-routes
Two things to know:
- Routes only. Upstream URL, upstream auth, the RS client and its secret,
the default scope and every stored per-user
user_secretare untouched, so no user reconnects. This is the cheap alternative to deleting and recreating the resource, which regenerates the RS client and drops stored credentials unless the resource is recreated under exactly the same name. - The array is replaced, not merged. A hand-edited route is overwritten by the connector's version of it.
A resource shows tool version unknown when it was created before this stamp
existed, or through POST /admin/api/managed-resources without declaring
connectorId / connectorVersion. Unknown offers the update rather than
claiming the tool list is current — the version an existing row's routes came
from is not recoverable, so it is reported honestly instead of guessed. A
resource that no bundled connector backs offers no update and returns 409 on
the endpoint: with no connector there is no source of truth to render from.
An MCP client that is already connected keeps the old tool list until it
reconnects, since clients cache tools/list.
Discovery endpoints
The gateway exposes standard OAuth discovery so MCP clients can find the AS automatically:
| Endpoint | Standard | Purpose |
|---|---|---|
GET /.well-known/oauth-protected-resource | RFC 9728 | Resource indicator, AS URLs, supported scopes |
GET /.well-known/oauth-authorization-server | RFC 8414 | Proxies the AS metadata document |
Next steps
- Start Here — dashboard onboarding: create an instance, provision a gateway, write policy, test
- MCP Protocol Flows — How clients discover, authenticate, and interact