Identity is the first decision MCPG makes on every request. Five identity plugins ship out of the box; you can chain them (e.g. mTLS first, fall back to API key) or run just one.
The plugins
| Plugin | When to use |
|---|---|
identity.api-key | Internal services, simple bootstrap, no IdP |
identity.basic | Legacy systems migrating from htpasswd |
identity.mtls | Service-to-service with X.509 certs from upstream TLS termination |
identity.oidc | Human users with Google / Okta / Auth0 / Entra |
identity.workload | Kubernetes / SPIFFE workloads with X.509-SVID + JWT-SVID |
Multiple plugins can run in a chain. The first one to resolve an identity wins; the rest short-circuit.
OIDC (Google, Okta, Auth0, Entra)
identity:
- id: oidc-google
type: identity.oidc
issuer: https://accounts.google.com
audience: mcpg-prod
jwks_url: https://www.googleapis.com/oauth2/v3/certs
jwks_cache_ttl: 1h
claim_mapping:
subject: email
groups: hd # workspace domain
The plugin pulls JWKS on first use, refreshes via a circuit breaker (no cascading failures if the IdP is down), and validates JWT bearer tokens on every request. SSRF guards prevent the JWKS URL from being weaponized.
mTLS
When MCPG sits behind a TLS-terminating proxy that injects client cert info as headers:
identity:
- id: mtls-front-door
type: identity.mtls
header: x-forwarded-client-cert
subject_field: cn
trusted_proxy_cidrs:
- 10.0.0.0/8
The plugin only accepts the header when the request comes from one of the trusted CIDRs. Otherwise the header is ignored.
For direct mTLS (no proxy), enable tls.client_cert_required on the gateway listener
itself; the plugin reads from the negotiated session.
SPIFFE workload identity
For Kubernetes / SPIRE deployments:
identity:
- id: workload
type: identity.workload
workload_api_socket: unix:///run/spire/agent/api.sock
trust_domain: example.org
audience: mcpg-prod
jwt_svid: true # accept JWT-SVIDs in addition to X.509-SVIDs
The plugin streams from the SPIRE Workload API, hot-reloads trust bundles, and stamps
each request with the resolved SPIFFE ID + selectors. Policy plugins downstream can then
authorize on spiffe://example.org/ns/prod/sa/payments.
API key
Simplest option for internal services:
identity:
- id: api-keys
type: identity.api-key
keys:
- id: alice
digest: $argon2id$v=19$m=65536,t=3,p=4$...
attributes:
team: platform
- id: ci-bot
digest: $argon2id$...
attributes:
team: ci
Generate digests with mcpg-ctl key generate. Constant-time comparison; no plaintext
ever stored.
Chaining
When you have multiple paths to identity:
identity:
- id: workload # try SPIFFE first (service-to-service)
type: identity.workload
# …
- id: oidc # fall back to OIDC (human users via UI)
type: identity.oidc
# …
- id: api-keys # fall back to API key (CI bots)
type: identity.api-key
# …
Order matters. The first plugin to return Resolved wins.
After identity: policy
Identity resolution produces a Caller struct with subject, attributes, and
audience. Policy plugins consume this. See the policy guide
next.