MCPG
Security
Security

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:

text
unauthenticated  <  header_asserted  <  verified
Trust levelSourceUse
unauthenticatedNo identity headers (anonymous)Public/health tooling only
header_assertedx-mcpg-subject-id header from a trusted upstreamBehind a mesh/proxy that has already authenticated
verifiedA cryptographically verified JWT / OIDC bearer, mTLS cert, or workload identityProduction 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:

  1. OIDC/OAuth — if governance.access.oidc_oauth is configured.
  2. JWKS — if governance.access.jwks is configured.
  3. Header-asserted — if a trusted x-mcpg-subject-id header is present.
  4. 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: none downgrade class.
  • Issuer + audience binding — a token is routed to the provider whose issuer matches, and its audiences must contain the gateway's expected audience. Cross-audience token replay is refused.
  • Three verification strategiesoidc_jwks (verify locally against cached JWKS), oauth_introspection (RFC 7662, for opaque tokens), and hybrid (JWKS first, fall back to introspection).
  • Clock-skew boundsexp / nbf are checked with a configurable skew tolerance.
  • Fail-closed caching — JWKS and discovery documents are cached and refreshed on an interval. A kid miss 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:

  1. Trust-level floor — caller's trust level must meet the tool's minimum_trust. Failure → JSON-RPC error -32003.
  2. Global CEL gategovernance.policy.tool_access.cel_allow_if must evaluate true. Failure → -32004.
  3. Per-tool CEL gate — the rule for this tool (if any) must evaluate true. Failure → -32005.
  4. 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 pluginsCedar, 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

ConcernConfig location
Native JWT/OIDC verificationgovernance.access.oidc_oauth, governance.access.jwks
Identity plugins (mTLS, API key, SPIFFE, basic)plugins[] with class: identity_provider
Trust floor + CEL gategovernance.policy.tool_access
Per-tool minimum trustmcp.capabilities.tools[].governance.minimum_trust
External policy enginesplugins[] with class: policy_engine

See the configuration reference for the full schema, and Plugin security for how MCPG verifies the plugins these blocks load.