Files
Flash5/flash-extensions/flash-ext-auth-oidc/docs/interop.md
T
Zakaria El Orche 829b9bf348 feat(ext-mcp): let applications put middleware on the MCP route, and document the auth split
McpExtension built its middleware chain entirely internally, so a consumer had no
way to add rate limiting, audit logging or tracing to /mcp — routine on every other
Flash route. McpConfig.middleware(...) appends to the chain after the transport
guards and after whatever McpSecurity resolved to, so it composes with OAuth2
protection instead of replacing it, and never satisfies REQUIRED.

Docs: flash-ext-auth-core and flash-ext-auth-oidc both get a docs/ directory —
oidc had none at all, and its module README documented types that no longer exist.
Includes a migration table from flash-ext-oidc.
2026-09-10 19:12:44 +00:00

4.0 KiB

Interop

flash-ext-auth-core

A hard dependency, and the reason this extension is as small as it is. The division:

Here flash-ext-auth-core
Discovery, JWKS, PKCE, token endpoint @Authenticated, @RolesAllowed, @ScopesAllowed
/login, /callback, /logout ClaimsHolder, Claims
Bearer and cookie resolution, refresh Role and scope matching
RFC 6750 WWW-Authenticate challenges Session, SessionStore

OidcExtension builds an OidcCredentialSource, hands it to AuthMiddleware.install(...), and that publishes the middleware and registers the annotation processor. Everything a handler annotation does is core's code running against claims this extension produced.

Consequence worth knowing: @RolesAllowed is not OIDC-specific and never was. An app that swaps this extension for another credential source keeps every annotation it had.

flash-ext-openapi

Optional, and resolved lazily so this extension runs standalone when openapi is not on the classpath. When it is, an OpenApiContributor is registered that emits an oauth2 security scheme with the authorizationCode flow, filled in from the discovery document:

"securitySchemes": {
  "myrealm": {
    "type": "oauth2",
    "flows": { "authorizationCode": { "authorizationUrl": "…", "tokenUrl": "…", "scopes": {} } }
  }
}

Per-operation security comes from the same annotations the middleware reads, so the spec and the enforcement cannot drift: both call AuthPolicy.compileFromAnnotations.

The scheme name is schemeName, derived from the last path segment of the issuer (a Keycloak realm name, usually) unless set explicitly.

flash-ext-mcp

McpSecurity asks whether this extension is installed — ctx.find(OidcCredentialSource.class) — and not merely whether something authenticates:

Policy this extension installed absent
REQUIRED protected boot fails
AUTO protected unprotected, warning logged
NONE never protected unprotected

That distinction is deliberate. REQUIRED means "a real OAuth2 authorization server is protecting this endpoint", because everything it turns on — RFC 9728 Protected Resource Metadata, RFC 8707 audience binding, WWW-Authenticate challenges carrying resource_metadata — is meaningless without an issuer. An app that authenticates some other way must not satisfy it by accident.

When it is installed, McpOidcIntegration derives the whole resource-server configuration from the source with no extra McpConfig calls:

  • the MCP route is wrapped with authMw.withSource(source.withResourceMetadata(path)).protect() — the same validation every other route uses, plus the resource_metadata challenge parameter;
  • an audience guard runs after it and rejects any token whose aud does not include this endpoint's resource identifier;
  • the resource identifier is resolved per request from X-Forwarded-Host/-Proto, or the Host header and selfScheme.

An app that does not use OAuth2 can still guard /mcp: set McpSecurity.NONE and pass its own guard to McpConfig.middleware(...).

Migrating from flash-ext-oidc

The module was renamed and its generic half moved. Mechanically:

Was Now
flash-ext-oidc (artifact) flash-ext-auth-oidc
dev.relism.flash.ext.oidc.Authenticated (and RolesAllowed, ScopesAllowed) dev.relism.flash.ext.auth.…
OidcMiddleware AuthMiddleware (dev.relism.flash.ext.auth)
ctx.find(OidcMiddleware.class) ctx.find(AuthMiddleware.class)
OidcUser Claims
ClaimsHolder.user() ClaimsHolder.current()
ClaimsHolder.get() ClaimsHolder.map()
OidcSession, OidcSessionStore, InMemoryOidcSessionStore Session, SessionStore, InMemorySessionStore
session.isAccessTokenExpired() session.isExpired()
session.idToken() session.attributeAsString(OidcCredentialSource.ID_TOKEN)

OidcConfig, OidcExtension and every setting on them are unchanged.