Identity and authorization
How MCPG establishes who a caller is and decides what they may do — a three-tier trust model, native JWT/OIDC verification, and a pre-dispatch authorization chain that fails closed.
Every request that reaches MCPG passes two gates before any backend is touched: first the gateway establishes who the caller is (identity), then it decides what that caller may do (authorization). Both are evaluated before dispatch, both fail closed, and both emit an audit event on every decision — allow or deny.
This page is the procurement-level overview. For hands-on configuration of each identity source and policy engine, see the Identity guide and the Policy guide.
Trust model
MCPG classifies every inbound request into one of three trust levels. The order is total — a caller is admitted at exactly one level, and authorization rules compare against it:
unauthenticated < header_asserted < verified
| Trust level | Source | Use |
|---|---|---|
unauthenticated | No identity headers (anonymous) | Public/health tooling only |
header_asserted | x-mcpg-subject-id header from a trusted upstream | Behind a mesh/proxy that has already authenticated |
verified | A cryptographically verified JWT / OIDC bearer, mTLS cert, or workload identity | Production callers |
A tool can declare a minimum trust level (governance.minimum_trust: verified);
a caller below that floor is denied before policy even runs.
Establishing identity
Identity resolution is first-match-wins, in this order:
- OIDC/OAuth — if
governance.access.oidc_oauthis configured. - JWKS — if
governance.access.jwksis configured. - Header-asserted — if a trusted
x-mcpg-subject-idheader is present. - Anonymous — fallback.
The gateway verifies JWT bearer tokens natively, with no plugin required.
Richer or chained sources (mTLS, API keys, SPIFFE/SPIRE workloads, basic auth)
load as identity_provider plugins and resolve in chain order.
Native JWT / OIDC verification
The OIDC resolver federates multiple identity providers with live discovery:
- Algorithm pinning — each provider declares
allowed_algs(e.g.["RS256"]). Tokens signed with any other algorithm are rejected, which closes the "alg confusion" /alg: nonedowngrade class. - Issuer + audience binding — a token is routed to the provider whose
issuermatches, and itsaudiencesmust contain the gateway's expected audience. Cross-audience token replay is refused. - Three verification strategies —
oidc_jwks(verify locally against cached JWKS),oauth_introspection(RFC 7662, for opaque tokens), andhybrid(JWKS first, fall back to introspection). - Clock-skew bounds —
exp/nbfare checked with a configurable skew tolerance. - Fail-closed caching — JWKS and discovery documents are cached and
refreshed on an interval. A
kidmiss triggers an immediate refresh; if a fetch fails and no cache (or stale-beyond-tolerance cache) exists, verification fails rather than admitting the token. - SSRF guard — discovery/JWKS callouts to private/loopback ranges are refused unless explicitly opted into for dev.
Supported signature algorithms: HS256/384/512, RS256/384/512, PS256/384/512,
ES256/384, EdDSA. Verified claims (subject, issuer, groups, roles, scopes,
arbitrary attributes) are mapped into the gateway's identity model via
claim_mappings and become available to the authorization layer.
For static JWKS (air-gapped), mTLS behind a terminating proxy, SPIFFE workload identity, and API keys, see the Identity guide.
Authorization
Once a caller is identified, the pre-dispatch policy gate decides whether the specific tool call is allowed. It runs before execution and short-circuits on the first denial:
- Trust-level floor — caller's trust level must meet the tool's
minimum_trust. Failure → JSON-RPC error-32003. - Global CEL gate —
governance.policy.tool_access.cel_allow_ifmust evaluate true. Failure →-32004. - Per-tool CEL gate — the rule for this tool (if any) must evaluate true.
Failure →
-32005. - Allow → proceed to dispatch.
CEL expressions evaluate against tool_name, trust_level, principal_id,
auth_provider, and identity_kind. The same gate also governs tool
visibility: a tool the caller may not invoke is hidden from tools/list, so
discovery never leaks the existence of tools the caller cannot use.
External policy engines
For richer authorization than CEL, MCPG runs policy-engine plugins —
Cedar, OPA (Rego), and Casbin — as a chain consulted before the
built-in trust-level gate on every tool.call.pre. The chain aggregates
conservatively (any Deny denies; an unreachable/empty chain falls through to
the trust-level safety net), so an external engine can only ever tighten
access, never loosen the floor. Chain them for defense-in-depth. See the
Policy guide for engine configuration and the
multi-tenant guide for per-tenant routing.
Secrets and credentials
Caller-facing config never carries plaintext secrets. Secret and credential
references (cred://, vault://, ${env.X}) resolve through secret-provider
plugins at config-load and request time, are compared in constant time where
applicable, and are redacted from logs. The audit trail records that a
credential was issued or read — never the secret material itself.
A hard invariant: cred:// references resolve only from config-origin
positions, and ${$env.X} interpolation is config-load-only. A caller cannot
smuggle a credential reference into a request argument to exfiltrate a secret.
Where this maps in config
| Concern | Config location |
|---|---|
| Native JWT/OIDC verification | governance.access.oidc_oauth, governance.access.jwks |
| Identity plugins (mTLS, API key, SPIFFE, basic) | plugins[] with class: identity_provider |
| Trust floor + CEL gate | governance.policy.tool_access |
| Per-tool minimum trust | mcp.capabilities.tools[].governance.minimum_trust |
| External policy engines | plugins[] with class: policy_engine |
See the configuration reference for the full schema, and Plugin security for how MCPG verifies the plugins these blocks load.