JWT Authentication
Since v0.13.0, flux7-mesh validates JWT tokens from external identity providers. mesh7 enforces — the IdP issues tokens.
How it works
External IdP (Auth0, Cloudflare Access, Keycloak)
│
└── issues JWT with agent identity in claims
│
▼
Agent ── Authorization: Bearer <jwt> ──► mesh7
│
1. Fetch JWKS (cached, refresh 5 min)
2. Validate signature (RS256 / ES256)
3. Check exp, iss, aud
4. Extract agent ID from claim
│
▼ policy engine (same YAML rules)
mesh7 does not issue tokens. It validates them against the IdP's public keys (JWKS endpoint) and extracts the agent identity from a configurable claim.
Configuration
auth:
jwt:
jwks_url: https://auth.example.com/.well-known/jwks.json
issuer: https://auth.example.com
audience: mesh7
agent_claim: sub
| Field | Required | Default | Description |
|---|---|---|---|
jwks_url |
Yes | — | JWKS endpoint URL (public keys) |
issuer |
No | — | Validate iss claim if set |
audience |
No | — | Validate aud claim if set |
agent_claim |
No | sub |
Which claim is used as agent ID for policies |
No auth.jwt block = no validation. The legacy Bearer agent:<name> format still works, backward compatible.
Data plane only
JWT governs agent identity on the data plane. Operator endpoints (traces, grants, approvals, policies) are guarded separately — see Control Plane Auth.
Auth flow
Authorization header present?
│
├─ No → anonymous
│
├─ "Bearer agent:<name>" → legacy path (no validation, name used as agent ID)
│
└─ "Bearer <jwt>" → validate against JWKS
├─ Valid → extract agent_claim → agent ID for policies
└─ Invalid → HTTP 401 Unauthorized
Key distinction: 401 (not authenticated — bad token) vs 403 (authenticated but policy denied).
JWKS caching
mesh7 fetches the JWKS at startup and caches it in memory. A background goroutine refreshes the cache every 5 minutes. No per-request fetch.
If the initial JWKS fetch fails, mesh7 does not start — fail-closed.
Supported algorithms
- RSA — RS256, RS384, RS512
- ECDSA — ES256 (P-256), ES384 (P-384), ES512 (P-521)
Tokens must include a kid header matching a key in the JWKS response.
Example: per-agent policies with JWT
auth:
jwt:
jwks_url: https://auth.example.com/.well-known/jwks.json
issuer: https://auth.example.com
agent_claim: sub
policies:
- name: admin-agents
agent: "admin-*"
rules:
- tools: ["*"]
action: allow
- name: worker-agents
agent: "worker-*"
rules:
- tools: ["filesystem.read_*"]
action: allow
- tools: ["filesystem.write_*"]
action: human_approval
- tools: ["*"]
action: deny
An agent with JWT claim sub: admin-bot matches the first policy. An agent with sub: worker-3 matches the second.
IdP examples
Cloudflare Access
auth:
jwt:
jwks_url: https://<team>.cloudflareaccess.com/cdn-cgi/access/certs
issuer: https://<team>.cloudflareaccess.com
audience: <application-aud-tag>
agent_claim: email
Auth0
auth:
jwt:
jwks_url: https://<tenant>.auth0.com/.well-known/jwks.json
issuer: https://<tenant>.auth0.com/
audience: https://mesh.example.com
Keycloak
auth:
jwt:
jwks_url: https://<host>/realms/<realm>/protocol/openid-connect/certs
issuer: https://<host>/realms/<realm>
audience: mesh7
Local development
For testing without a real IdP, use the generate-token.py script in examples/jwt-auth/ :
pip install cryptography PyJWT
# Start a local JWKS server
python generate-token.py serve &
# Generate a token
TOKEN=$(python generate-token.py token --sub my-agent)
# Test
curl -H "Authorization: Bearer $TOKEN" http://localhost:9090/health
See examples/jwt-auth/ for the full setup.
MCP Streamable HTTP
JWT validation also applies to MCP Streamable HTTP connections (POST /mcp). Managed Agents or remote MCP clients presenting a valid JWT are identified by their claim value — same policies apply.