Class OfflineAuthorizationServer
- All Implemented Interfaces:
AutoCloseable
secured MCP example (and its end-to-end test) run entirely offline — no external IdP, no
network access, no pre-shared secrets checked into source control.
DEMO ONLY — NOT SUITABLE FOR PRODUCTION USE.
This is deliberately NOT a general-purpose or spec-complete authorization server. It exists purely to make
SecuredExampleMcpServer's JwtTokenValidator-backed resource-server gate and
SecuredExampleClient's McpTokenProvider-backed client-credentials flow both have something
real to talk to. Concretely, on start() it:
- Generates a fresh RSA-2048 signing key (a new key every run — nothing is persisted, so restarting the example invalidates any previously-issued token, which is exactly the offline-demo property we want).
- Publishes only the public half of that key as a
JWKSource, whichSecuredExampleMcpServerfeeds directly toJwtTokenValidator.jwkSource(...)— no JWKS HTTP endpoint is needed because the server and "AS" share this JVM. - Starts a tiny
HttpServerexposing two routes:POST /token— the RFC 6749 §4.4 client-credentials grant against one fixed, randomly-generated demo client id/secret pair (seeclientId()/clientSecret()). This is a REAL HTTP round trip: the client-sideOAuthClientCredentialsFlowused byMcpTokenProvidertalks to it exactly as it would talk to a production IdP's token endpoint, just onlocalhost.GET /.well-known/oauth-authorization-server— a minimal RFC 8414 Authorization Server Metadata document (issuer+token_endpointonly), so a real client can perform genuine discovery againstissuerUri()instead of being handed the token endpoint out of band. SeeSecuredExampleClientfor the client half of that handshake.
What this class intentionally does NOT implement (called out here and in the README so nobody mistakes this demo for a template for a real deployment):
- No real scope-authorization decision —
handleToken(com.sun.net.httpserver.HttpExchange)only checks the requested scope against a fixed allowlist (mcp.read/mcp.write); it never asks a resource owner or policy engine whether the client is ALLOWED that scope, which is what a real AS's consent/policy step does. - No refresh tokens, no consent screen, no user authentication of any kind — only the machine-to-machine client-credentials grant, which is all an MCP server-to-server OAuth demo needs.
- No persistence, replay protection beyond the JWT
exp/nbfwindow, or key rotation. The RFC 8414 document above is likewise minimal: nojwks_uri, noscopes_supported, no dynamic client registration — this AS's public key is handed directly to the validator in-process (seejwkSource()) rather than published for a real JWKS fetch.
TLS caveat: this class and SecuredExampleServer talk plain http only because both
ends are loopback-only (127.0.0.1/localhost). OAuth 2.1 / RFC 9728 require https for
any non-loopback resource identifier or token endpoint; a bearer token sent over plaintext to a non-loopback
host is exposed to anyone on the network path.
A real deployment would point JwtTokenValidator at an actual IdP's JWKS URL and issuer, and would
never generate or hand out client secrets like start() does here.
- Since:
- 10.0.0
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringThe scope minted onto a token when the client's request omits one.The fixed allowlist of scopes this offline AS will grant (H2): a client requesting anything outside this set is rejected with RFC 6749 §5.2invalid_scope, rather than being handed back whatever it asked for. -
Method Summary
Modifier and TypeMethodDescriptionclientId()Returns the fixed demo client id a caller must present (via HTTP Basic) to acquire a token.Returns the randomly-generated demo client secret a caller must present (via HTTP Basic) to acquire a token.voidclose()Returns this authorization server's issuer identity (its own loopback base URL) — used as the JWTissclaim, the PRM document'sauthorization_serversentry, and the RFC 8414 discovery document'sissuerfield.com.nimbusds.jose.jwk.source.JWKSource<com.nimbusds.jose.proc.SecurityContext>Returns aJWKSourcepublishing only the public half of this server's signing key, ready to hand straight toJwtTokenValidator.jwkSource(...).static OfflineAuthorizationServerstart()Generates a fresh signing key and demo client credentials, and starts the/tokenand RFC 8414 discovery endpoints on an OS-assigned loopback-only port.Returns the loopback URL of the/tokenclient-credentials endpoint.intReturns how many HTTP requests have hit the/tokenendpoint so far (M7): a test seam proving a cachingMcpTokenProvidergenuinely reuses an acquired token across multiple dispatches instead of silently re-requesting one per call.
-
Field Details
-
DEFAULT_SCOPE
The scope minted onto a token when the client's request omits one.- See Also:
-
GRANTABLE_SCOPES
The fixed allowlist of scopes this offline AS will grant (H2): a client requesting anything outside this set is rejected with RFC 6749 §5.2invalid_scope, rather than being handed back whatever it asked for.
-
-
Method Details
-
start
Generates a fresh signing key and demo client credentials, and starts the/tokenand RFC 8414 discovery endpoints on an OS-assigned loopback-only port. -
jwkSource
Returns aJWKSourcepublishing only the public half of this server's signing key, ready to hand straight toJwtTokenValidator.jwkSource(...).- Returns:
- The public-key JWK source. Never
null .
-
issuerUri
Returns this authorization server's issuer identity (its own loopback base URL) — used as the JWTissclaim, the PRM document'sauthorization_serversentry, and the RFC 8414 discovery document'sissuerfield.- Returns:
- The issuer URI. Never
null .
-
tokenEndpoint
Returns the loopback URL of the/tokenclient-credentials endpoint.- Returns:
- The token endpoint URL. Never
null .
-
clientId
Returns the fixed demo client id a caller must present (via HTTP Basic) to acquire a token.- Returns:
- The client id. Never
null .
-
clientSecret
Returns the randomly-generated demo client secret a caller must present (via HTTP Basic) to acquire a token. Generated fresh on everystart(); never persisted anywhere —SecuredExampleServer.main(java.lang.String[])prints it to the console purely so a reader running the demo by hand has something to copy/paste.- Returns:
- The client secret. Never
null .
-
tokenRequestCount
Returns how many HTTP requests have hit the/tokenendpoint so far (M7): a test seam proving a cachingMcpTokenProvidergenuinely reuses an acquired token across multiple dispatches instead of silently re-requesting one per call.- Returns:
- The number of
/tokenrequests handled sincestart().
-
close
- Specified by:
closein interfaceAutoCloseable
-