Skip to content

Add AI agents authentication deep dive#3284

Open
fiunchinho wants to merge 8 commits into
mainfrom
muster-auth-deep-dive
Open

Add AI agents authentication deep dive#3284
fiunchinho wants to merge 8 commits into
mainfrom
muster-auth-deep-dive

Conversation

@fiunchinho

Copy link
Copy Markdown
Member

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 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 (github-mcp), and how in-cluster agents act on behalf of a human.

All content fact-checked against muster, mcp-oauth, mcp-kubernetes, and klaus-gateway on main as 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.

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.
@fiunchinho fiunchinho self-assigned this Jul 16, 2026
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.
@fiunchinho
fiunchinho marked this pull request as ready for review July 16, 2026 16:28
@fiunchinho
fiunchinho requested a review from a team as a code owner July 16, 2026 16:28
- 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)
@fiunchinho
fiunchinho requested a review from a team July 16, 2026 16:40
fiunchinho and others added 4 commits July 16, 2026 18:48
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.
Comment on lines +120 to +134
### 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer a shorter handle in the menu here

Image
Suggested change
linkTitle: Authentication deep dive
linkTitle: Authentication

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the title not be agent platform instead or AI Agent?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Comment on lines +39 to +57
### 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it mean all Dex instances are configured with the audience of the central Dex?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants