Atoti Intelligence SDK
This is part of the Atoti Intelligence SDK offer.self-issued OAuth 2.1 mode, available from Atoti Server
6.2.0, on the Atoti MCP Server, where the Atoti Server acts as its own authorization server. MCP
clients (Claude Code, Claude Desktop, Cursor, Cline, VS Code’s MCP support, Gemini CLI) drive the
standard browser-based PKCE flow directly against Atoti. No external identity provider is required.
Users sign in with their existing Atoti credentials. No separate IdP, no extra user store.
What is self-issued mode?
The Atoti MCP Server supports two OAuth 2.1 modes, selected byatoti.server.endpoint.mcp.oauth2.mode:
In
self-issued mode the Atoti Server is both the OAuth 2.1 authorization server and the resource
server. It publishes the standard metadata documents, handles Dynamic Client Registration, and
issues JWTs signed with Atoti’s own keys. The MCP client’s authorization flow is identical to
external mode; only the server the client talks to changes.
Prerequisites
- The Atoti MCP Server is already running. See How to set up the Atoti MCP Server.
- Atoti Server is configured with an RSA keypair via
atoti.jwt.key.*(recommended). When no keypair is configured, an ephemeral key is generated at startup (see Signing keys for the implications).
Minimal configuration
Self-issued mode requires only four lines. Add the following toapplication.yml:
How does the browser PKCE flow work?
The following steps describe the full authorization flow as a client such as Claude Code experiences it.- The client sends
POST /mcpand receives401withWWW-Authenticate: Bearer realm="mcp", resource_metadata="...". - The client fetches
/.well-known/oauth-protected-resource(RFC 9728). The document advertises the same origin as the authorization server. - The client fetches
/.well-known/oauth-authorization-server(RFC 8414). The document advertisesauthorization_endpoint,token_endpoint,registration_endpoint,jwks_uri, and PKCES256support. - The client performs anonymous Dynamic Client Registration at
POST /oauth2/register(also accepted atPOST /register). It receives a generatedclient_id. - The client opens the system browser at
/oauth2/authorize. The user is redirected to Atoti’s own branded login page at/loginand signs in with existing Atoti credentials. - After sign-in, the browser is shown a consent page at
/oauth2/consenttitled “Allow Atoti to connect to <client name>?” listing the requested scopes (for example,mcp.read,mcp.write). The user clicks Allow or Deny. - On Allow, the browser redirects back to the client’s loopback URI with an authorization
code. On Deny,
access_deniedis returned to the client and no token is issued. - The client exchanges the code at
POST /oauth2/tokenfor a JWT access token and a refresh token. - The client calls
POST /mcpwithAuthorization: Bearer <jwt>. Atoti validates the token and the user’s Atoti roles authorize the requested MCP tools.
Which endpoints does self-issued mode expose?
Whenmode=self-issued, the following endpoints become active in addition to the standard
/mcp endpoint:
How does self-issued mode work?
How does self-issued mode handle identity and keys?
Self-issued mode reuses Atoti’s existing identity stack. End users authenticate against the same user store that secures the rest of the Atoti deployment (the application’s configuredUserDetailsService or authentication manager). The issued JWTs are signed with Atoti’s own RSA
keypair, validated by Atoti’s normal JWT filter, and carry the user’s real Atoti roles into /mcp
authorization. No separate key material or user store is needed.
Self-issued mode requires the application to provide a UserDetailsService (or authentication
manager) bean. Atoti deployments already supply one: the basic-authentication fallback registers a
UserDetailsService whenever any authentication is configured. For local development without a full
deployment, the apps/basic sample application supplies an in-memory user store. If no
UserDetailsService is configured, the server still starts but logs a warning, and the browser
login cannot authenticate users until one is provided:
atoti.server.endpoint.mcp.oauth2.mode=self-issued is active but no UserDetailsService is configured, so the OAuth 2.1 browser login cannot authenticate users.
Which signing key does self-issued mode use?
The server selects the signing key in the following order of precedence:- Atoti’s configured RSA keypair (recommended). Set via
atoti.jwt.key.*oractiveviam.jwt.key.*. The same key signs MCP tokens and all other Atoti JWTs. - A PKCS#12 or JKS keystore, configured via
atoti.server.endpoint.mcp.oauth2.authserver.jwk.keystore-location,...keystore-password, and...key-alias. - An ephemeral keypair, generated at startup. This fallback is for development only: all issued tokens become invalid on restart, and a warning is logged at startup.
How is the issuer URI configured?
atoti.server.endpoint.mcp.oauth2.authserver.issuer is optional. When unset, the issuer and all
advertised absolute URLs are derived from the incoming HTTP request. Set it explicitly when the
server runs behind a reverse proxy that rewrites the host or scheme, or enable Spring Boot’s
ForwardedHeaderFilter via server.forward-headers-strategy=framework.
Which MCP transport does self-issued mode use?
The starter configuresspring.ai.mcp.server.protocol=STREAMABLE by default, which exposes a
single POST /mcp endpoint. This is the transport expected by current MCP clients, and the one to
use everywhere.
How does user consent work in self-issued mode?
After a user authenticates, the browser is shown a consent page at/oauth2/consent titled
“Allow Atoti to connect to <client name>?”. The page lists the requested OAuth scopes (for
example, mcp.read, mcp.write) and presents Allow and Deny buttons.
- Allow completes the flow: an authorization code is issued and the client exchanges it for a JWT.
- Deny returns
access_deniedto the client; no token is issued.
/oauth2/consent unauthenticated is redirected to the Atoti login page first. A non-browser
caller receives 401.
CSRF protection. The Allow/Deny decision is protected by a single-use session-bound consent key.
The key is generated when the gate stashes the pending authorization request, and consumed on the
subsequent /oauth2/authorize pass. An attacker cannot forge a consent POST without knowing that
key, which lives only in the server-side session. For production deployments, also set
server.servlet.session.cookie.same-site=Lax so the session cookie is not sent on cross-site
navigations, adding defense in depth against CSRF on the consent endpoint.
To disable the consent step and restore the previous behavior (a code is issued immediately after
authentication), set atoti.server.endpoint.mcp.oauth2.authserver.consent.enabled to false.
Property reference
All properties are under theatoti.server.endpoint.mcp.oauth2 prefix unless noted.
Which limitations apply to self-issued mode?
Open Dynamic Client Registration. The/oauth2/register endpoint accepts unauthenticated
registration requests by design: any caller can register a client, but no token is issued until a
user completes interactive login at /oauth2/authorize. For deployments with strict registration
policies, place the endpoint behind rate-limiting or a registration filter.
In-memory client store. Dynamically registered clients are held in memory. They are lost on
server restart, but clients re-register transparently on the next connection attempt.
Ephemeral signing key. If no RSA keypair is configured (neither atoti.jwt.key.* nor a
keystore), an ephemeral key is generated at startup. All tokens issued with that key become invalid
on restart. A warning is logged. Use a persistent key in production.
Token lifetime. Access tokens default to one hour. Configure
atoti.server.endpoint.mcp.oauth2.authserver.access-token-ttl to a shorter value in
security-sensitive environments.
When should each OAuth 2.1 mode be used?
Related reading
- How to set up the Atoti MCP Server
- Configure OAuth 2.1 discovery (
externalmode with a corporate IdP) - Connect with Claude
- RFC 8414: OAuth 2.0 Authorization Server Metadata
- RFC 7591: OAuth 2.0 Dynamic Client Registration Protocol
- RFC 9728: OAuth 2.0 Protected Resource Metadata
- MCP authorization spec (2025-06-18)