Add AI agents authentication deep dive#3284
Conversation
A token-by-token explanation of how user identity flows from an MCP client through Muster and mcp-kubernetes to the Kubernetes API: the login redirect chain, per-request token validation, what each token contains, forward and exchange downstream modes, impersonation for workload clusters, direct Dex tokens for non-interactive clients, outbound OAuth for backends with a different identity provider, and how in-cluster agents act on behalf of a human. Fact-checked against muster, mcp-oauth, mcp-kubernetes, and klaus-gateway on main as of 2026-07-16.
Tokens become colored stadium nodes between the participants (amber opaque token, blue ID token), matching the visual language of the original design; participant cards trimmed to two lines; supporting systems dashed. If approved, the exchange and oauth diagrams get the same treatment.
The mermaid flowchart renditions demoted the tokens to edge labels; the original design's point is tokens as first-class colored chips between the participants. Inline the original HTML (the site renders raw HTML), with every selector scoped under .hop-flow so nothing can collide with the site stylesheet. The sequence diagrams stay mermaid.
- Backtick code identifiers in prose (client_id, client_secret, staticClients, kagent, userinfo) - Add legitimate domain words to the Vale vocabulary (deprovisioned, lookups, reconnections, URIs, validator, walkthrough) - Apply Microsoft style: unspaced em dashes, contractions, capitalized first word after heading colons, 'for example' over 'e.g.', punctuation inside quotes, no '(s)' plurals - Re-indent mermaid bodies to two spaces (MD046) and drop the trailing period in the 'Dex' step heading (MD026)
Vale substitutes ignored tokens (code spans, emphasis, links) with whitespace, so a dash touching markup registers as a spaced dash. Reword the eight affected spots so dashes only sit between plain words.
reviewdog fails the vale job outright when a page produces more
annotations than it can post ('Too many results in diff'), so a new
page must arrive style-clean, not merely error-free. Split ~67 long
and semicolon-joined sentences, drop trailing question marks from
headings (Hugo anchors unchanged), replace above/below with
earlier/later, and apply the remaining adverb and wordiness
suggestions. No technical content changed; verified zero findings
with the pinned Vale image locally.
| ### 1. Does the Muster session artifact still exist and hold | ||
|
|
||
| Muster will try to find the token in the token storage. If the token isn't found, or it has expired or it has been revoked, the validation fails. This enables the system to perform instant-revocation: killing the store entry ends the session now. | ||
|
|
||
| ### 2. Is the underlying Dex session still alive | ||
|
|
||
| The stored Dex token is checked for expiry. If it's expired but a Dex refresh token exists, Muster refreshes against Dex right there. If Dex refuses the refresh (for example, user deprovisioned, session revoked at the IdP) the request fails. So if users are removed at the upstream identity provider (Google / Azure / etc.), their access will be cut off when the Dex token expires. This happens even though Muster's own artifacts (first check in this list) haven't expired. | ||
|
|
||
| ### 3. Does Dex vouch for the user right now | ||
|
|
||
| On every request coming from MCP clients, Muster will call the `/userinfo` endpoint in Dex to check if the Dex access token is still valid at the Dex level. This catches revocation at Dex immediately (session deleted or revoked at Dex). Note that this check only proves the session is still alive at Dex. A user removed at the upstream identity provider (Google / Azure) isn't visible to `/userinfo`. Dex itself only finds out the next time it refreshes against the upstream, which is [check 2 described earlier](#2-is-the-underlying-dex-session-still-alive). Each layer vouches for itself in real time. News from the upstream layer arrives at refresh time. | ||
|
|
||
| ### 4. Was this token minted for this server | ||
|
|
||
| When it mints a token, Muster records in its storage which resource the token was requested for (its own resource URI—this is OAuth's "resource indicator," RFC 8707). At validation time it checks that binding, so a token minted for a different resource server is rejected even if it's otherwise valid. |
There was a problem hiding this comment.
For such step-by-step content we have the HUGO shortcode steps and step. Would be nice to use it here. You'll find examples in the code base.
|
|
||
| ### 1. Claude Code → Muster | ||
|
|
||
| When Claude Code gets a `401 Unauthorized` HTTP response, the response carries a `WWW-Authenticate` header pointing at the server's metadata, which is how Claude Code discovers the authorization server and its endpoints. Using that information, Claude Code (the client) opens your browser at Muster's `/oauth/authorize`, identifying itself with its `client_id`. For MCP clients that `client_id` isn't a pre-registered value but a URL the client controls (see ["How Claude Code becomes a client without pre-registration"](#how-claude-code-becomes-a-client-without-pre-registration) below). |
There was a problem hiding this comment.
| When Claude Code gets a `401 Unauthorized` HTTP response, the response carries a `WWW-Authenticate` header pointing at the server's metadata, which is how Claude Code discovers the authorization server and its endpoints. Using that information, Claude Code (the client) opens your browser at Muster's `/oauth/authorize`, identifying itself with its `client_id`. For MCP clients that `client_id` isn't a pre-registered value but a URL the client controls (see ["How Claude Code becomes a client without pre-registration"](#how-claude-code-becomes-a-client-without-pre-registration) below). | |
| When Claude Code gets a `401 Unauthorized` HTTP response, the response carries a `WWW-Authenticate` header pointing at the server's metadata, which is how Claude Code discovers the Authorization server (AS) and its endpoints. Using that information, Claude Code (the client) opens your browser at Muster's `/oauth/authorize`, identifying itself with its `client_id`. For MCP clients, that `client_id` isn't a pre-registered value but a URL the client controls (see ["How Claude Code becomes a client without pre-registration"](#how-claude-code-becomes-a-client-without-pre-registration) below). |
|
|
||
| ### 5. The chain unwinds | ||
|
|
||
| The identity provider redirects the browser back to Dex's callback with a code. Dex exchanges it (authenticating with its identity provider / connector `client_secret`), mints its own tokens, and redirects the browser to Muster's `/oauth/callback` with a new code. Muster exchanges that code at Dex's `/token` endpoint, authenticating with Muster's Dex `client_secret`, and receives the Dex ID token. Muster then redirects the browser one last time to Claude Code's localhost callback with yet another code. When Claude Code exchanges that code, Muster mints and returns its own access token. |
There was a problem hiding this comment.
IMO it is a bit confusing, explaning why exchange the cods may help users to understand why so many tokens are involved
| @@ -0,0 +1,716 @@ | |||
| --- | |||
| title: 'Authentication deep dive: from your MCP client to the Kubernetes API' | |||
| linkTitle: Authentication deep dive | |||
There was a problem hiding this comment.
Should the title not be agent platform instead or AI Agent?
There was a problem hiding this comment.
it makes me think fleet management is now some kind of misdirection right? It is a fleet of what ?
|
|
||
| ### 4. Was this token minted for this server | ||
|
|
||
| When it mints a token, Muster records in its storage which resource the token was requested for (its own resource URI—this is OAuth's "resource indicator," RFC 8707). At validation time it checks that binding, so a token minted for a different resource server is rejected even if it's otherwise valid. |
There was a problem hiding this comment.
| When it mints a token, Muster records in its storage which resource the token was requested for (its own resource URI—this is OAuth's "resource indicator," RFC 8707). At validation time it checks that binding, so a token minted for a different resource server is rejected even if it's otherwise valid. | |
| When Dex mints a token, Muster records in its storage which resource the token was requested for (its own resource URI—this is OAuth's "resource indicator," RFC 8707). At validation time, it checks that binding, so a token minted for a different resource server is rejected even if it's otherwise valid. |
| ### 1. Claude Code → Muster | ||
|
|
||
| When Claude Code gets a `401 Unauthorized` HTTP response, the response carries a `WWW-Authenticate` header pointing at the server's metadata, which is how Claude Code discovers the authorization server and its endpoints. Using that information, Claude Code (the client) opens your browser at Muster's `/oauth/authorize`, identifying itself with its `client_id`. For MCP clients that `client_id` isn't a pre-registered value but a URL the client controls (see ["How Claude Code becomes a client without pre-registration"](#how-claude-code-becomes-a-client-without-pre-registration) below). | ||
|
|
||
| ### 2. Muster → Dex | ||
|
|
||
| Muster doesn't know your password and doesn't want to. Instead of showing a login form, it redirects that same browser onward to Dex's `/auth` endpoint. Now Muster acts as Dex's client, using the client_id/client_secret that was pre-registered for Muster in Dex's configuration (a `staticClients` entry in Dex's config. Muster reads its copy from its own config). | ||
|
|
||
| ### 3. Dex → the upstream identity provider | ||
|
|
||
| Dex is doing the exact same trick one level deeper: it's an AS to Muster but a client of the identity provider, for example Google. Each Dex "connector" holds a client_id/secret registered with the upstream identity provider. It redirects the browser to the identity provider's login page. | ||
|
|
||
| ### 4. User logs in | ||
|
|
||
| You type your password at the identity provider login page, and only there. Neither Dex, nor Muster, nor Claude Code ever sees it. | ||
|
|
||
| ### 5. The chain unwinds | ||
|
|
||
| The identity provider redirects the browser back to Dex's callback with a code. Dex exchanges it (authenticating with its identity provider / connector `client_secret`), mints its own tokens, and redirects the browser to Muster's `/oauth/callback` with a new code. Muster exchanges that code at Dex's `/token` endpoint, authenticating with Muster's Dex `client_secret`, and receives the Dex ID token. Muster then redirects the browser one last time to Claude Code's localhost callback with yet another code. When Claude Code exchanges that code, Muster mints and returns its own access token. |
There was a problem hiding this comment.
This also looks like a good case for step/steps (see my other comment)
|
|
||
| Similarly, `mcp-kubernetes` is configured to accept tokens that contain `muster` in the audience field, via the `trustedAudiences` list in `mcp-kubernetes` configuration. This is empty by default (deny-by-default: each aggregator is an explicit entry). | ||
|
|
||
| The same audience discipline appears one more time, in the token exchange scenario. When Muster exchanges tokens with a target Dex, the target Dex is itself a validator of an incoming ID token—one whose audience is `muster`, not the target Dex. It accepts it because its connector is explicitly configured with which audience to expect on incoming tokens: Muster's client id at the source Dex. A Dex instance has no audience of its own as an issuer. Like every other validator in this document, it accepts exactly the audiences its configuration names, and nothing else. |
There was a problem hiding this comment.
Does it mean all Dex instances are configured with the audience of the central Dex?

Adds a new explanation page under Overview → AI agents: a token-by-token walkthrough of how user identity flows from an MCP client through Muster and mcp-kubernetes to the Kubernetes API.
Covers: the login redirect chain (CIMD, PKCE, Dex, upstream IdP), Muster's per-request token validation, what each token actually contains (annotated examples), the
forwardandexchangedownstream modes, impersonation for workload clusters, direct Dex tokens for non-interactive clients, outbound OAuth for backends with a different identity provider (github-mcp), and how in-cluster agents act on behalf of a human.All content fact-checked against
muster,mcp-oauth,mcp-kubernetes, andklaus-gatewayonmainas of 2026-07-16 (post muster#947/#968: Muster issues only opaque tokens and never signs JWTs; Dex is the sole SSO authority).Supersedes the intranet version in giantswarm/giantswarm#37117, which will be closed in favor of this public page.
Complements the existing Security and Architecture pages — this one is the deep dive; those stay the quick answers.