Skip to content

docs(security): document client credentials for machine-to-machine callers - #191

Open
adityamparikh wants to merge 1 commit into
apache:mainfrom
adityamparikh:docs/keycloak-client-credentials
Open

docs(security): document client credentials for machine-to-machine callers#191
adityamparikh wants to merge 1 commit into
apache:mainfrom
adityamparikh:docs/keycloak-client-credentials

Conversation

@adityamparikh

Copy link
Copy Markdown
Contributor

What's missing today

Every token example in docs/security/keycloak.md uses the password grant with testuser. A
service that calls this MCP server on its own behalf — an agent, a scheduled job, another
application — has no user to authenticate as, and no documented path. The confidential client it
needs is already described under Creating Clients; what was missing is the grant that uses it.

The part that actually bites

The audience mapper. The Quick Start attaches it to the public client used by MCP Inspector, so
a service client created by following Creating Clients has none of its own:

token issued normally  →  server answers 401  →  nothing in the token request hinted at a problem

validateAudienceClaim(true) finds no matching aud and rejects it. The new Machine-to-Machine
Tokens
section states where the mapper belongs, shows adding it to the service client, and shows
confirming that the claim landed before an application is pointed at it.

Also fixed: the audience check under Troubleshooting

echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | jq

A JWT payload is base64url without padding, so base64 -d truncates it and jq reports
parse error: Unfinished string at EOF — which reads like a malformed token when the token is
perfectly fine. That is a bad failure mode for a troubleshooting step whose whole job is telling you
whether the token is good. Replaced with a jq-only decode that tolerates the missing padding:

echo "$TOKEN" | jq -R 'split(".")[1] | @base64d | fromjson'

Verification

Every command in the new section was run verbatim against Keycloak 26 and a running Solr MCP server,
and produces the output shown in the doc:

Step Result
client_credentials grant against the confidential client token issued
aud check ["http://localhost:8080/mcp", "account"]
/.well-known/oauth-protected-resource http://localhost:8080/mcp
Old decode idiom parse error: Unfinished string at EOF
New decode idiom claim printed

Docs only — no code or test changes.

🤖 Generated with Claude Code

https://claude.ai/code/session_011nUD34DFfoJeyQRTquPy7a

…llers

Every token example in keycloak.md uses the password grant with a test user, so
a service calling this server on its own behalf -- an agent, a scheduled job,
another application -- has no documented path. The confidential client it needs
is already described under Creating Clients; what was missing is the grant that
uses it.

The gap that actually bites is the audience mapper. The Quick Start attaches it
to the public client used by MCP Inspector, so a service client created by
following Creating Clients has none of its own, and its tokens are issued
normally and then rejected with 401 by validateAudienceClaim(true). The new
section says where the mapper belongs, shows how to add it to the service
client, and shows how to confirm the claim landed.

Also fixes the audience check under Troubleshooting. A JWT payload is base64url
without padding, so `base64 -d` truncates it and jq reports "Unfinished string
at EOF" instead of the claim -- the failure looks like a malformed token when
the token is fine. Replaced with a jq-only decode that tolerates the missing
padding.

Every command in the new section was run verbatim against Keycloak 26 and a
running server, and produces the output shown.

Signed-off-by: Aditya Parikh <aditya.m.parikh@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011nUD34DFfoJeyQRTquPy7a
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.

1 participant