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.
flash-ext-auth-oidc
OpenID Connect for Flash: the authorization-code flow with PKCE, JWKS-validated bearer tokens, server-side sessions with silent refresh, and single logout.
It is a credential source for flash-ext-auth-core,
which owns everything downstream of "who is this caller" — @Authenticated, @RolesAllowed,
@ScopesAllowed, ClaimsHolder. Installing this extension installs that machinery too; you do not
install flash-ext-auth-core yourself.
Quick start
app.install(new OidcExtension(
OidcConfig.builder(
"https://keycloak.example.com/realms/myrealm",
"my-app", "secret", "/auth/callback")
.rolesClaimPath("realm_access.roles")
.https()
.build()));
That is the whole integration. Discovery runs at boot and fails fast if the issuer is unreachable, so a misconfigured provider is a startup crash rather than a 500 on the first login.
OidcConfig.fromEnv() reads the same settings from OIDC_* environment variables, and
OidcConfig.keycloak(serverUrl, realm, ...) builds the issuer URL for you.
What it registers
GET {prefix}/login |
Builds the authorization URL with PKCE + state and redirects |
GET {prefix}/callback |
Validates state and nonce, exchanges the code, creates the session |
POST {prefix}/logout |
Ends the session and redirects to the provider's end-session endpoint |
{prefix} is routePrefix (default /auth). Logout is a POST on purpose — a GET logout is
one <img> tag away from being triggered by any page the user visits.
In the context it provides AuthMiddleware (from auth-core), OidcCredentialSource and
JwtValidator.
How a request is resolved
Authorization: Bearer …— validated against the issuer's JWKS.oidc_sessioncookie — looked up in theSessionStore; if the access token has expired and a refresh token is present, refreshed transparently and the session replaced.- Neither, and the client sent
Accept: application/json→401with aWWW-Authenticate: Bearerchallenge. - Neither, and it looks like a browser → redirect to
{prefix}/login?redirect={path}.
Points 3 and 4 are why the source distinguishes "no credential" from "bad credential": an API client must not be redirected into an HTML sign-in page, and a browser must not be left staring at a bare 401.
Configuration
| Setting | Default | Notes |
|---|---|---|
issuer, clientId, clientSecret, redirectUri |
— | required |
scopes |
openid profile email |
|
routePrefix |
/auth |
|
selfScheme |
http |
https() behind TLS; only used when no X-Forwarded-Proto |
rolesClaimPath |
realm_access.roles |
Keycloak's spelling; groups for Authelia |
scopeClaimPaths |
scope,scp |
comma-separated, tried in order |
algorithm |
RS256 |
|
postLogoutRedirectUri |
/ |
|
sessionStore |
InMemorySessionStore |
swap for Redis/JDBC across instances |
clientAuthMethod |
POST |
token endpoint client authentication |
insecureTls() |
off | dev only, skips certificate validation |
schemeName |
derived from the issuer | OpenAPI security scheme name |
A relative redirectUri (starting with /) is resolved per request against the incoming Host,
or X-Forwarded-Host/-Proto when behind a proxy — so one build works in dev and behind TLS
without a second configuration.
Sessions
A session holds the claims plus the access, id and refresh tokens, the last three in
Session.attributes() under this extension's own keys. Core never reads them; renewal happens
here. See ../../flash-ext-auth-core/docs/sessions.md.
Multiple providers
Two issuers on one server, each with its own route prefix:
app.install(new OidcExtension(tenantAConfig)) // routePrefix("/tenantA/auth")
.install(new OidcExtension(tenantBConfig)); // routePrefix("/tenantB/auth")
Both are known at boot. Registering an issuer at runtime — a customer connecting their own IdP from a settings page — is not supported.
Interop
See interop.md for how this extension fits with flash-ext-auth-core,
flash-ext-openapi and flash-ext-mcp.