docs(security): show how a service client obtains its token - #191
Open
adityamparikh wants to merge 1 commit into
Open
docs(security): show how a service client obtains its token#191adityamparikh wants to merge 1 commit into
adityamparikh wants to merge 1 commit into
Conversation
adityamparikh
force-pushed
the
docs/keycloak-client-credentials
branch
from
September 11, 2026 13:55
5ba7d50 to
a2cd8bf
Compare
The Spring AI MCP client section registers a confidential client in step 1 and
then verifies a tool call with `$TOKEN` in step 4, but nothing between the two
ever obtains that token by hand. The client_credentials request only appears
Spring-side, as properties and an OAuth2AuthorizedClientManager bean, so the
first time a reader finds out whether the registration is correct is after
writing application code -- and a misapplied audience mapper surfaces there as
an opaque 401 from the transport rather than as a missing `aud` claim.
Adds the two Admin API calls that read back the generated secret and the
client_credentials request that exchanges it, immediately after the
registration they check. Reuses the `jwt_payload` helper already defined under
Configuring the Audience Claim to assert the `aud` claim landed, which is the
single check that separates a working service client from one that will fail
later.
Also warns about the 300s token lifetime here specifically. Section 3 mentions
it as a reason not to hand-roll a token cache, and section 4 warns about an
unset `$TOKEN`, but neither covers a *stale* one -- and this text is what hands
the reader a `$TOKEN` to carry forward several minutes of reading. Both tokens
in play expire, `$ADMIN_TOKEN` included, and an expired token is rejected with
the same 401 as a misconfigured mapper. Writing this section produced that
exact confusion twice, so the WWW-Authenticate check that disambiguates them is
included.
The console path to the secret is kept alongside the API calls; the rest of the
section is scripted, so only having a click-path there was the odd one out.
Verified against Keycloak 26.0.8, Solr 9.10.1 and the server on the http
profile with HTTP_SECURITY_ENABLED=true. Every command was run verbatim:
the client registration returned 201, the secret read back over the Admin API,
the client_credentials grant issued a token whose `aud` was
["http://localhost:8080/mcp", "account"], /actuator/metrics answered 200 with
that token and 401 without it, and the step 4 check-health tool call returned
{"isHealthy":true,...} with the token and "Access Denied" without.
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_01NxXQyT9mLgFV8TPUU4imwp
adityamparikh
force-pushed
the
docs/keycloak-client-credentials
branch
from
September 11, 2026 14:14
a2cd8bf to
bed06fc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The gap
Configuring a Spring AI MCP Client registers a confidential client in step 1 and verifies a gated tool call with
$TOKENin step 4, but nothing between them obtains that token. Theclient_credentialsrequest appears only Spring-side, as properties and anOAuth2AuthorizedClientManagerbean, so the first time a reader learns whether the registration is right is after writing application code, and aprotocolMappersblock that did not take surfaces as an opaque401rather than a missingaudclaim.The change
Appended to step 1: the two Admin API calls that read back the generated secret, the
client_credentialsrequest that exchanges it, and anaudassertion using the existingjwt_payloadhelper. A note on the 300 s token lifetime tells the reader to re-request before step 4, and theWWW-Authenticatecheck distinguishes an expired token from a misconfigured mapper (both are a401). The console click-path to the secret is kept alongside the API calls.Verification
Every command run verbatim against Keycloak 26.0.8, Solr 9.10.1, and the server on the
httpprofile withHTTP_SECURITY_ENABLED=true:spring-ai-app(§1 curl)201CLIENT_UUID/CLIENT_SECRETover Admin APIclient_credentialsgrantjwt_payload "$TOKEN" | jq .aud["http://localhost:8080/mcp", "account"]/actuator/metricswith token / without200/401check-healthtool call with token{"isHealthy":true,...}check-healthtool call without token{"content":[{"text":"Access Denied"}],"isError":true}WWW-Authenticateerror_description="... Jwt expired at ..."Docs only; no code or test changes.
🤖 Generated with Claude Code
https://claude.ai/code/session_01CiUHyyXLTo9ATdgg8eRFZJ