agentic-community / agentic-community/mcp-gateway-registry

Per-user token revocation denylist (admin kill-switch) for gateway-issued MCP tokens

Open
#1,699 2 comments 0 reactions 1 assignee Claimed by @aarora79 View on GitHub
authentication credentials-management enhancement security
Dominant language
Python
Stars
911
Forks
234
Avg merge
1d 11h
Merged PRs (30d)
62

Description

## Problem

Gateway-issued MCP data-plane tokens are **self-signed** (HS256, `iss=mcp-auth-server`; the "Get JWT Token" / MCP-config modal bakes one into `mcp.json`). Because the gateway is the issuer, they have **no revocation path**: when a user is removed/disabled at the IdP, their still-valid gateway token keeps working until TTL expiry (code default 8h; deployments often 2-3h; absolute cap 7d). The only existing kill-switch is rotating `SECRET_KEY`, which invalidates **every** user's token at once.

The IdP-signed-tokens approach (closed PR #1692, issue #991) would make IdP revocation propagate automatically, but it requires OAuth/DCR client onboarding that is infeasible on no-DCR IdPs such as Microsoft Entra. We need a simple, provider-agnostic, **admin-driven** workflow: *"this user was removed from the IdP → immediately disable all their tokens."*

## Proposed solution

A gateway-side **per-user revocation denylist**, keyed on the canonical per-user identity (`_canonical_egress_user`, which resolves both self-signed and IdP tokens to the same provider-agnostic OIDC `sub`):

- **Admin API** to revoke / un-revoke / list. Revoke-by-email with server-side resolution to the canonical `sub` (the canonical id is a GUID on Entra, so admins shouldn't hand-copy it).
- **Store:** a MongoDB collection with a **TTL index** (`expireAfterSeconds`, native datetime) so entries auto-expire. TTL must be >= the max token lifetime (absolute cap 7d) so a revoked user's still-valid token can't outlive its denylist entry.
- **Enforcement at `/validate`** (nginx `auth_request` hot path): after token validation, if the caller's canonical id is on the denylist, return **403** (mirrors the existing `_enforce_rate_limit`/quarantine gate; keyed on the validated token, never a client header). Because it keys on the user (not a token), it also blocks any newly-minted token. A short in-process cache with bounded-stale fallback keeps the hot path cheap and revocation near-real-time.
- **Reuse** existing patterns: the rate-limit quarantine kill-switch (enforcement/cache shape) and the TTL-index repository pattern (audit / egress-operational repositories).

## Scope / decisions
- Applies to **all per-user tokens** (self-signed + IdP); non-per-user methods (`federation-static`, `network-trusted`) are skipped.
- Flag `MCP_USER_REVOCATION_ENABLED` (default **true**, inert when the denylist is empty; set `false` to disable the check).
- Fail-mode: **fail-open on cold cache + bounded-stale + startup warm-load** — a store outage must not 403 every user out of MCP; a prior revocation stays effective from the cached set during a brief outage.

## Acceptance criteria
- [ ] Admin-only API can revoke (by email→canonical sub), un-revoke, and list.
- [ ] After revoke, `/validate` returns 403 for the user's existing and new tokens within the cache-staleness window; after un-revoke, access is restored.
- [ ] Entries auto-expire via a TTL index (>= max token lifetime).
- [ ] Revoke/un-revoke are admin-only, CSRF-protected, and audit-logged (acting admin + target).
- [ ] Inert/backwards-compatible when no users are revoked; negligible `/validate` latency (cached).
- [ ] Metrics: revoked-users gauge + denials counter.

## Non-goals
- Automatic IdP-synced revocation (that is the deferred IdP-signed direction; PR #1692 / #991).
- Per-token (single-session) revocation — this is per-user.
- Any DCR / OAuth client onboarding.

## Related
- Closed PR #1692 and #991/#988 (IdP-signed / RFC 8707) — the deferred alternative; this is a different, admin-driven way to meet the same revocation need without DCR.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.