diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index aa0097f..cf88791 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -17,8 +17,8 @@
-
-
+
+
diff --git a/README.md b/README.md
index 10ac1e4..89fc701 100644
--- a/README.md
+++ b/README.md
@@ -11,8 +11,8 @@ a zero-allocation FSM router, bounded protocol state, and one shared request/res
| `flash-testing` | JUnit 5 harness — boot an app on an ephemeral port, fake its services, assert on responses |
| `flash-extensions/flash-ext-jackson` | Jackson JSON integration |
| `flash-extensions/flash-ext-openapi` | OpenAPI 3.0 spec + Swagger UI |
-| `flash-extensions/flash-ext-oidc` | OIDC Authorization Code + PKCE flow |
-| `flash-extensions/flash-ext-mcp` | MCP (Model Context Protocol) server — Streamable HTTP, optional OAuth2 via flash-ext-oidc |
+| `flash-extensions/flash-ext-auth-oidc` | OIDC Authorization Code + PKCE flow |
+| `flash-extensions/flash-ext-mcp` | MCP (Model Context Protocol) server — Streamable HTTP, optional OAuth2 via flash-ext-auth-oidc |
| `flash-extensions/flash-ext-view-core` | Minimal shared SSR runtime primitives |
| `flash-extensions/flash-ext-view-jte` | Opinionated jte SSR extension |
| `flash-extensions/flash-ext-view-thymeleaf` | Opinionated Thymeleaf SSR extension |
@@ -146,7 +146,7 @@ FlashApp.create(8080)
See extension-specific READMEs for full details:
- [`flash-ext-jackson`](flash-extensions/flash-ext-jackson/README.md)
- [`flash-ext-openapi`](flash-extensions/flash-ext-openapi/README.md)
-- [`flash-ext-oidc`](flash-extensions/flash-ext-oidc/README.md)
+- [`flash-ext-auth-oidc`](flash-extensions/flash-ext-auth-oidc/README.md)
- [`flash-ext-mcp`](flash-extensions/flash-ext-mcp/docs/README.md)
- [`flash-ext-view-jte`](flash-extensions/flash-ext-view-jte/README.md)
- [`flash-ext-view-thymeleaf`](flash-extensions/flash-ext-view-thymeleaf/README.md)
diff --git a/flash-extensions/flash-ext-auth-core/src/main/java/dev/relism/flash/ext/auth/AuthMiddleware.java b/flash-extensions/flash-ext-auth-core/src/main/java/dev/relism/flash/ext/auth/AuthMiddleware.java
index 30c7a4d..2c4a104 100644
--- a/flash-extensions/flash-ext-auth-core/src/main/java/dev/relism/flash/ext/auth/AuthMiddleware.java
+++ b/flash-extensions/flash-ext-auth-core/src/main/java/dev/relism/flash/ext/auth/AuthMiddleware.java
@@ -5,6 +5,8 @@ import dev.relism.flash.extension.FlashContext;
import dev.relism.flash.models.Request;
import dev.relism.flash.models.Response;
import dev.relism.flash.routing.Middleware;
+import dev.relism.flash.routing.MiddlewareKey;
+import dev.relism.flash.routing.MiddlewareNode;
import java.util.ArrayList;
import java.util.List;
@@ -28,6 +30,13 @@ import java.util.Map;
*/
public class AuthMiddleware {
+ /**
+ * Boot-time identity of the node annotation-driven authorization mounts under. Public so an
+ * extension that contributes its own middleware can order itself around authentication —
+ * {@code MiddlewareNode.of(...).afterIfPresent(AuthMiddleware.POLICY)}.
+ */
+ public static final MiddlewareKey POLICY = MiddlewareKey.of("flash.auth.policy");
+
private final AuthConfig config;
private final CredentialSource source;
private final String[] roleClaimPathParts;
@@ -40,6 +49,26 @@ public class AuthMiddleware {
this.scopeClaimPathParts = splitClaimPaths(config.scopeClaimPaths());
}
+ /**
+ * Builds the middleware for {@code source}, publishes it in the context and registers the
+ * annotation processor that mounts {@link Authenticated}, {@link RolesAllowed} and
+ * {@link ScopesAllowed} on scanned handlers.
+ *
+ *
Every extension that contributes a {@link CredentialSource} calls this rather than
+ * repeating the wiring — the processor and the {@link #POLICY} key belong to one place.
+ */
+ public static AuthMiddleware install(FlashContext ctx, AuthConfig config, CredentialSource source) {
+ AuthMiddleware middleware = new AuthMiddleware(config, source);
+ ctx.provide(AuthMiddleware.class, middleware);
+ ctx.addAnnotationProcessor(handlerClass -> {
+ AuthPolicy policy = AuthPolicy.compileFromAnnotations(handlerClass);
+ return policy != null
+ ? List.of(MiddlewareNode.of(POLICY, middleware.authorize(policy)))
+ : List.of();
+ });
+ return middleware;
+ }
+
// -- Public API -----------------------------------------------------------
/** The single configured claim path used by every transport for role checks. */
diff --git a/flash-extensions/flash-ext-auth-core/src/main/java/dev/relism/flash/ext/auth/InMemorySessionStore.java b/flash-extensions/flash-ext-auth-core/src/main/java/dev/relism/flash/ext/auth/InMemorySessionStore.java
new file mode 100644
index 0000000..eff0fa1
--- /dev/null
+++ b/flash-extensions/flash-ext-auth-core/src/main/java/dev/relism/flash/ext/auth/InMemorySessionStore.java
@@ -0,0 +1,20 @@
+package dev.relism.flash.ext.auth;
+
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Thread-safe in-memory {@link SessionStore}.
+ *
+ *
Sessions are lost on restart and not shared across instances. For
+ * production deployments with multiple nodes or restart-persistence requirements,
+ * supply another implementation to whichever {@link CredentialSource} owns the session.
+ */
+public final class InMemorySessionStore implements SessionStore {
+
+ private final ConcurrentHashMap store = new ConcurrentHashMap<>();
+
+ @Override public void save(Session s) { store.put(s.id(), s); }
+ @Override public Optional find(String id) { return Optional.ofNullable(store.get(id)); }
+ @Override public void delete(String id) { store.remove(id); }
+}
diff --git a/flash-extensions/flash-ext-auth-core/src/main/java/dev/relism/flash/ext/auth/Session.java b/flash-extensions/flash-ext-auth-core/src/main/java/dev/relism/flash/ext/auth/Session.java
new file mode 100644
index 0000000..0a77824
--- /dev/null
+++ b/flash-extensions/flash-ext-auth-core/src/main/java/dev/relism/flash/ext/auth/Session.java
@@ -0,0 +1,53 @@
+package dev.relism.flash.ext.auth;
+
+import java.time.Instant;
+import java.util.Map;
+
+/**
+ * A signed-in caller's server-side session — saved in a {@link SessionStore} and looked up by a
+ * cookie on every request.
+ *
+ * Immutable: renewing one produces a new instance that replaces the old under the same
+ * {@link #id()}.
+ *
+ *
{@link #attributes()} is whatever the {@link CredentialSource} needs to keep alongside the
+ * claims and nothing this module interprets — OpenID Connect stores its access, id and refresh
+ * tokens there so that renewal is its business rather than core's.
+ */
+public final class Session {
+
+ /** Renew this far before the real expiry, so a session cannot lapse mid-request. */
+ private static final long EAGER_RENEWAL_SECONDS = 30;
+
+ private final String id;
+ private final Map claims;
+ private final Instant expiresAt;
+ private final Map attributes;
+
+ public Session(String id, Map claims, Instant expiresAt,
+ Map attributes) {
+ this.id = id;
+ this.claims = Map.copyOf(claims);
+ this.expiresAt = expiresAt;
+ this.attributes = attributes == null ? Map.of() : Map.copyOf(attributes);
+ }
+
+ /** True once the session is within {@value #EAGER_RENEWAL_SECONDS} seconds of expiring. */
+ public boolean isExpired() {
+ return Instant.now().isAfter(expiresAt.minusSeconds(EAGER_RENEWAL_SECONDS));
+ }
+
+ /** One attribute, or {@code null} when the source never stored it. */
+ public Object attribute(String key) { return attributes.get(key); }
+
+ /** One attribute as a String, or {@code null}. */
+ public String attributeAsString(String key) {
+ Object v = attributes.get(key);
+ return v != null ? v.toString() : null;
+ }
+
+ public String id() { return id; }
+ public Map claims() { return claims; }
+ public Instant expiresAt() { return expiresAt; }
+ public Map attributes() { return attributes; }
+}
diff --git a/flash-extensions/flash-ext-auth-core/src/main/java/dev/relism/flash/ext/auth/SessionStore.java b/flash-extensions/flash-ext-auth-core/src/main/java/dev/relism/flash/ext/auth/SessionStore.java
new file mode 100644
index 0000000..2e0a058
--- /dev/null
+++ b/flash-extensions/flash-ext-auth-core/src/main/java/dev/relism/flash/ext/auth/SessionStore.java
@@ -0,0 +1,13 @@
+package dev.relism.flash.ext.auth;
+
+import java.util.Optional;
+
+/**
+ * Where {@link Session}s live between requests. {@link InMemorySessionStore} is the default;
+ * supply another for Redis, JDBC, or anything that survives a restart or spans instances.
+ */
+public interface SessionStore {
+ void save(Session session);
+ Optional find(String sessionId);
+ void delete(String sessionId);
+}
diff --git a/flash-extensions/flash-ext-auth-core/src/test/java/dev/relism/flash/ext/auth/SessionTest.java b/flash-extensions/flash-ext-auth-core/src/test/java/dev/relism/flash/ext/auth/SessionTest.java
new file mode 100644
index 0000000..6aef288
--- /dev/null
+++ b/flash-extensions/flash-ext-auth-core/src/test/java/dev/relism/flash/ext/auth/SessionTest.java
@@ -0,0 +1,58 @@
+package dev.relism.flash.ext.auth;
+
+import org.junit.jupiter.api.Test;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The two things a session has to get right: it reports itself expired early enough that it
+ * cannot lapse midway through a request, and it hands back what a credential source stored on it
+ * without interpreting any of it.
+ */
+class SessionTest {
+
+ private static Session at(Instant expiry, Map attributes) {
+ return new Session("s1", Map.of("sub", "u1"), expiry, attributes);
+ }
+
+ @Test
+ void aSessionIsExpiredWellBeforeItsDeadline() {
+ // The eager window is what stops a session from lapsing between the check and the handler.
+ assertFalse(at(Instant.now().plusSeconds(120), Map.of()).isExpired());
+ assertTrue(at(Instant.now().plusSeconds(10), Map.of()).isExpired());
+ assertTrue(at(Instant.now().minusSeconds(1), Map.of()).isExpired());
+ }
+
+ @Test
+ void attributesAreReturnedUninterpreted() {
+ Session session = at(Instant.now().plusSeconds(60), Map.of("oidc.id_token", "abc", "n", 7));
+ assertEquals("abc", session.attributeAsString("oidc.id_token"));
+ assertEquals("7", session.attributeAsString("n"));
+ assertEquals(7, session.attribute("n"));
+ assertNull(session.attributeAsString("absent"));
+ }
+
+ @Test
+ void aSessionWithoutAttributesIsUsableRatherThanNull() {
+ assertNull(at(Instant.now().plusSeconds(60), null).attributeAsString("anything"));
+ }
+
+ @Test
+ void claimsAndAttributesAreCopiedAndImmutable() {
+ Map mutable = new HashMap<>(Map.of("k", "v"));
+ Session session = at(Instant.now().plusSeconds(60), mutable);
+ mutable.put("k", "changed");
+
+ assertEquals("v", session.attributeAsString("k"));
+ assertThrows(UnsupportedOperationException.class, () -> session.attributes().put("x", "y"));
+ assertThrows(UnsupportedOperationException.class, () -> session.claims().put("x", "y"));
+ }
+}
diff --git a/flash-extensions/flash-ext-oidc/README.md b/flash-extensions/flash-ext-auth-oidc/README.md
similarity index 99%
rename from flash-extensions/flash-ext-oidc/README.md
rename to flash-extensions/flash-ext-auth-oidc/README.md
index 8382dce..5fccedf 100644
--- a/flash-extensions/flash-ext-oidc/README.md
+++ b/flash-extensions/flash-ext-auth-oidc/README.md
@@ -1,4 +1,4 @@
-# flash-ext-oidc
+# flash-ext-auth-oidc
Full OIDC Authorization Code + PKCE flow for the Flash HTTP server.
Supports Keycloak, Authelia, Auth0, Google, and any RFC 8414-compliant provider.
@@ -25,7 +25,7 @@ hot-path model (middleware compiled at mount time, no heavy runtime work).
```xml
dev.relism
- flash-ext-oidc
+ flash-ext-auth-oidc
1.0-SNAPSHOT
```
diff --git a/flash-extensions/flash-ext-oidc/pom.xml b/flash-extensions/flash-ext-auth-oidc/pom.xml
similarity index 96%
rename from flash-extensions/flash-ext-oidc/pom.xml
rename to flash-extensions/flash-ext-auth-oidc/pom.xml
index 9967473..43aa29a 100644
--- a/flash-extensions/flash-ext-oidc/pom.xml
+++ b/flash-extensions/flash-ext-auth-oidc/pom.xml
@@ -10,7 +10,7 @@
2.1.0-SNAPSHOT
- flash-ext-oidc
+ flash-ext-auth-oidc
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/ClientAuthMethod.java b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/ClientAuthMethod.java
similarity index 100%
rename from flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/ClientAuthMethod.java
rename to flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/ClientAuthMethod.java
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/DiscoveryClient.java b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/DiscoveryClient.java
similarity index 100%
rename from flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/DiscoveryClient.java
rename to flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/DiscoveryClient.java
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/JwtUtils.java b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/JwtUtils.java
similarity index 100%
rename from flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/JwtUtils.java
rename to flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/JwtUtils.java
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/JwtValidator.java b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/JwtValidator.java
similarity index 100%
rename from flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/JwtValidator.java
rename to flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/JwtValidator.java
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcConfig.java b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcConfig.java
similarity index 96%
rename from flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcConfig.java
rename to flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcConfig.java
index 837af03..997b71a 100644
--- a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcConfig.java
+++ b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcConfig.java
@@ -1,5 +1,8 @@
package dev.relism.flash.ext.oidc;
+import dev.relism.flash.ext.auth.InMemorySessionStore;
+import dev.relism.flash.ext.auth.SessionStore;
+
/**
* Full OIDC client configuration. Build via
* {@link #builder(String, String, String, String)} or {@link #fromEnv()}.
@@ -50,7 +53,7 @@ public final class OidcConfig {
private final String scopeClaimPaths;
private final String algorithm;
private final String postLogoutRedirectUri;
- private final OidcSessionStore sessionStore;
+ private final SessionStore sessionStore;
private final boolean insecureTls;
private final ClientAuthMethod clientAuthMethod;
private final String schemeName;
@@ -68,7 +71,7 @@ public final class OidcConfig {
this.algorithm = b.algorithm;
this.postLogoutRedirectUri = b.postLogoutRedirectUri;
this.sessionStore = b.sessionStore != null ? b.sessionStore
- : new InMemoryOidcSessionStore();
+ : new InMemorySessionStore();
this.insecureTls = b.insecureTls;
this.clientAuthMethod = b.clientAuthMethod;
this.schemeName = b.schemeName != null ? b.schemeName : deriveScheme(this.issuer);
@@ -88,7 +91,7 @@ public final class OidcConfig {
public String scopeClaimPaths() { return scopeClaimPaths; }
public String algorithm() { return algorithm; }
public String postLogoutRedirectUri() { return postLogoutRedirectUri; }
- public OidcSessionStore sessionStore() { return sessionStore; }
+ public SessionStore sessionStore() { return sessionStore; }
/** If {@code true}, TLS certificate validation is skipped. Never use in production. */
public boolean insecureTls() { return insecureTls; }
public ClientAuthMethod clientAuthMethod() { return clientAuthMethod; }
@@ -190,7 +193,7 @@ public final class OidcConfig {
private String scopeClaimPaths = "scope,scp";
private String algorithm = "RS256";
private String postLogoutRedirectUri = "/";
- private OidcSessionStore sessionStore;
+ private SessionStore sessionStore;
private boolean insecureTls = false;
private ClientAuthMethod clientAuthMethod = ClientAuthMethod.POST;
private String schemeName = null;
@@ -218,8 +221,8 @@ public final class OidcConfig {
public Builder algorithm(String algorithm) { this.algorithm = algorithm; return this; }
/** Where to redirect after logout (default: {@code /}). */
public Builder postLogoutRedirectUri(String uri) { this.postLogoutRedirectUri = uri; return this; }
- /** Custom session store (default: {@link InMemoryOidcSessionStore}). */
- public Builder sessionStore(OidcSessionStore store) { this.sessionStore = store; return this; }
+ /** Custom session store (default: {@link InMemorySessionStore}). */
+ public Builder sessionStore(SessionStore store) { this.sessionStore = store; return this; }
/**
* Disables TLS certificate verification for all HTTP calls made by this extension.
* Only use in development with self-signed certificates — never in production.
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcCredentialSource.java b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcCredentialSource.java
similarity index 85%
rename from flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcCredentialSource.java
rename to flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcCredentialSource.java
index 36f6adf..cd95e63 100644
--- a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcCredentialSource.java
+++ b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcCredentialSource.java
@@ -2,6 +2,7 @@ package dev.relism.flash.ext.oidc;
import dev.relism.flash.exceptions.HttpException;
import dev.relism.flash.ext.auth.CredentialSource;
+import dev.relism.flash.ext.auth.Session;
import dev.relism.flash.models.Response;
import dev.relism.flash.models.Request;
@@ -20,7 +21,7 @@ import java.util.Optional;
* Resolution order on each request:
*
* - {@code Authorization: Bearer ...} header — validated via JWKS ({@link JwtValidator}).
- * - {@code oidc_session} cookie — looked up in {@link OidcSessionStore}; transparently
+ *
- {@code oidc_session} cookie — looked up in {@link dev.relism.flash.ext.auth.SessionStore}; transparently
* refreshed if the access token is expired.
* - Browser clients (no {@code Accept: application/json}) → redirect to
* {@code {routePrefix}/login?redirect={path}}.
@@ -31,6 +32,24 @@ public final class OidcCredentialSource implements CredentialSource {
private static final String BEARER = "Bearer";
+ /**
+ * Keys this source stores its OAuth2 tokens under in {@link Session#attributes()}. Core keeps
+ * the session; the tokens inside it are nobody else's business.
+ */
+ static final String ACCESS_TOKEN = "oidc.access_token";
+ static final String ID_TOKEN = "oidc.id_token";
+ static final String REFRESH_TOKEN = "oidc.refresh_token";
+
+ /** The one place an OIDC session is built, so its attribute keys stay in one place too. */
+ static Session newSession(String id, String accessToken, String idToken, String refreshToken,
+ Instant expiresAt, Map claims) {
+ Map attributes = new HashMap<>(3);
+ if (accessToken != null) attributes.put(ACCESS_TOKEN, accessToken);
+ if (idToken != null) attributes.put(ID_TOKEN, idToken);
+ if (refreshToken != null) attributes.put(REFRESH_TOKEN, refreshToken);
+ return new Session(id, claims, expiresAt, attributes);
+ }
+
private final JwtValidator validator;
private final OidcConfig config;
private final OidcProviderMetadata meta;
@@ -108,14 +127,14 @@ public final class OidcCredentialSource implements CredentialSource {
String sessionId = cookieValue(req, "oidc_session");
if (sessionId != null) {
- Optional found = config.sessionStore().find(sessionId);
+ Optional found = config.sessionStore().find(sessionId);
if (found.isPresent()) {
- OidcSession session = found.get();
- if (!session.isAccessTokenExpired())
+ Session session = found.get();
+ if (!session.isExpired())
return session.claims();
- if (session.refreshToken() != null) {
+ if (session.attributeAsString(REFRESH_TOKEN) != null) {
try {
- OidcSession refreshed = doRefresh(session);
+ Session refreshed = doRefresh(session);
config.sessionStore().save(refreshed);
return refreshed.claims();
} catch (Exception ignored) { }
@@ -149,17 +168,17 @@ public final class OidcCredentialSource implements CredentialSource {
// 2. Session cookie
String sessionId = cookieValue(req, "oidc_session");
if (sessionId != null) {
- Optional found = config.sessionStore().find(sessionId);
+ Optional found = config.sessionStore().find(sessionId);
if (found.isPresent()) {
- OidcSession session = found.get();
+ Session session = found.get();
- if (!session.isAccessTokenExpired())
+ if (!session.isExpired())
return session.claims();
// Access token expired — try silent refresh
- if (session.refreshToken() != null) {
+ if (session.attributeAsString(REFRESH_TOKEN) != null) {
try {
- OidcSession refreshed = doRefresh(session);
+ Session refreshed = doRefresh(session);
config.sessionStore().save(refreshed);
return refreshed.claims();
} catch (Exception ignored) {
@@ -184,19 +203,17 @@ public final class OidcCredentialSource implements CredentialSource {
return null;
}
- private OidcSession doRefresh(OidcSession old) throws Exception {
+ private Session doRefresh(Session old) throws Exception {
OidcTokenResponse tokens = tokenClient.refresh(
- meta.tokenEndpoint(), old.refreshToken());
+ meta.tokenEndpoint(), old.attributeAsString(REFRESH_TOKEN));
- Map claims = mergeRefreshedClaims(tokens, old);
-
- return new OidcSession(
+ return newSession(
old.id(),
tokens.accessToken(),
- tokens.idToken() != null ? tokens.idToken() : old.idToken(),
- tokens.refreshToken() != null ? tokens.refreshToken() : old.refreshToken(),
+ tokens.idToken() != null ? tokens.idToken() : old.attributeAsString(ID_TOKEN),
+ tokens.refreshToken() != null ? tokens.refreshToken() : old.attributeAsString(REFRESH_TOKEN),
Instant.now().plusSeconds(tokens.expiresIn()),
- claims
+ mergeRefreshedClaims(tokens, old)
);
}
@@ -280,7 +297,7 @@ public final class OidcCredentialSource implements CredentialSource {
return out.toString();
}
- private static Map mergeRefreshedClaims(OidcTokenResponse tokens, OidcSession old) {
+ private static Map mergeRefreshedClaims(OidcTokenResponse tokens, Session old) {
Map merged = new HashMap<>();
// Fall back to old claims first, then overlay fresh token claims
merged.putAll(old.claims());
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcExtension.java b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcExtension.java
similarity index 95%
rename from flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcExtension.java
rename to flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcExtension.java
index 133c152..65605f0 100644
--- a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcExtension.java
+++ b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcExtension.java
@@ -5,6 +5,7 @@ import dev.relism.flash.ext.openapi.OpenApiContributorRegistry;
import dev.relism.flash.ext.openapi.OpenApiOperationContribution;
import dev.relism.flash.ext.openapi.OpenApiResponseContribution;
import dev.relism.flash.ext.auth.AuthConfig;
+import dev.relism.flash.ext.auth.Session;
import dev.relism.flash.ext.auth.AuthMiddleware;
import dev.relism.flash.ext.auth.AuthPolicy;
import dev.relism.flash.ext.auth.Authenticated;
@@ -13,8 +14,6 @@ import dev.relism.flash.ext.auth.ScopesAllowed;
import dev.relism.flash.extension.FlashContext;
import dev.relism.flash.extension.FlashExtension;
import dev.relism.flash.extension.FlashRegistrar;
-import dev.relism.flash.routing.MiddlewareKey;
-import dev.relism.flash.routing.MiddlewareNode;
import dev.relism.flash.models.Request;
import javax.net.ssl.SSLContext;
@@ -63,7 +62,6 @@ import java.util.*;
* }
*/
public class OidcExtension implements FlashExtension {
- private static final MiddlewareKey POLICY = MiddlewareKey.of("flash.auth.policy");
private final OidcConfig config;
@@ -96,19 +94,14 @@ public class OidcExtension implements FlashExtension {
stateStore = new OidcStateStore();
tokenClient = new TokenClient(http, config);
source = new OidcCredentialSource(validator, config, meta, tokenClient);
- authMw = new AuthMiddleware(AuthConfig.builder()
+ authMw = AuthMiddleware.install(ctx, AuthConfig.builder()
.rolesClaimPath(config.rolesClaimPath())
.scopeClaimPaths(config.scopeClaimPaths())
.build(), source);
- ctx.provide(AuthMiddleware.class, authMw);
ctx.provide(OidcCredentialSource.class, source);
ctx.provide(JwtValidator.class, validator);
- ctx.addAnnotationProcessor(handlerClass -> {
- AuthPolicy policy = AuthPolicy.compileFromAnnotations(handlerClass);
- return policy != null ? List.of(MiddlewareNode.of(POLICY, authMw.authorize(policy))) : List.of();
- });
ctx.onReady(() -> registerRoutes(app, ctx));
}
@@ -177,7 +170,7 @@ public class OidcExtension implements FlashExtension {
}
Map claims = mergeClaims(tokens);
- OidcSession session = new OidcSession(
+ Session session = OidcCredentialSource.newSession(
UUID.randomUUID().toString(),
tokens.accessToken(), tokens.idToken(), tokens.refreshToken(),
Instant.now().plusSeconds(tokens.expiresIn()), claims);
@@ -195,8 +188,8 @@ public class OidcExtension implements FlashExtension {
String idTokenHint = null;
if (sessionId != null) {
- OidcSession session = config.sessionStore().find(sessionId).orElse(null);
- if (session != null) idTokenHint = session.idToken();
+ Session session = config.sessionStore().find(sessionId).orElse(null);
+ if (session != null) idTokenHint = session.attributeAsString(OidcCredentialSource.ID_TOKEN);
config.sessionStore().delete(sessionId);
}
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcProviderMetadata.java b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcProviderMetadata.java
similarity index 100%
rename from flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcProviderMetadata.java
rename to flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcProviderMetadata.java
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcStateStore.java b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcStateStore.java
similarity index 100%
rename from flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcStateStore.java
rename to flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcStateStore.java
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcTokenResponse.java b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcTokenResponse.java
similarity index 100%
rename from flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcTokenResponse.java
rename to flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcTokenResponse.java
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcValidationException.java b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcValidationException.java
similarity index 100%
rename from flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcValidationException.java
rename to flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcValidationException.java
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/PkceUtils.java b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/PkceUtils.java
similarity index 100%
rename from flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/PkceUtils.java
rename to flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/PkceUtils.java
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/TokenClient.java b/flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/TokenClient.java
similarity index 100%
rename from flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/TokenClient.java
rename to flash-extensions/flash-ext-auth-oidc/src/main/java/dev/relism/flash/ext/oidc/TokenClient.java
diff --git a/flash-extensions/flash-ext-oidc/src/test/java/dev/relism/flash/ext/oidc/OidcCredentialSourceTest.java b/flash-extensions/flash-ext-auth-oidc/src/test/java/dev/relism/flash/ext/oidc/OidcCredentialSourceTest.java
similarity index 100%
rename from flash-extensions/flash-ext-oidc/src/test/java/dev/relism/flash/ext/oidc/OidcCredentialSourceTest.java
rename to flash-extensions/flash-ext-auth-oidc/src/test/java/dev/relism/flash/ext/oidc/OidcCredentialSourceTest.java
diff --git a/flash-extensions/flash-ext-oidc/src/test/java/dev/relism/flash/ext/oidc/OidcOpenApiInteropTest.java b/flash-extensions/flash-ext-auth-oidc/src/test/java/dev/relism/flash/ext/oidc/OidcOpenApiInteropTest.java
similarity index 100%
rename from flash-extensions/flash-ext-oidc/src/test/java/dev/relism/flash/ext/oidc/OidcOpenApiInteropTest.java
rename to flash-extensions/flash-ext-auth-oidc/src/test/java/dev/relism/flash/ext/oidc/OidcOpenApiInteropTest.java
diff --git a/flash-extensions/flash-ext-mcp/docs/README.md b/flash-extensions/flash-ext-mcp/docs/README.md
index 643ce0b..4022668 100644
--- a/flash-extensions/flash-ext-mcp/docs/README.md
+++ b/flash-extensions/flash-ext-mcp/docs/README.md
@@ -3,7 +3,7 @@
`flash-ext-mcp` turns a Flash5 app into an [MCP](https://modelcontextprotocol.io) (Model Context
Protocol) server: JSON-RPC 2.0 over the Streamable HTTP transport, tools/resources/prompts
declared as plain classes and discovered at boot, optional OAuth2 protection built on
-`flash-ext-oidc`.
+`flash-ext-auth-oidc`.
## Quick Start
@@ -44,7 +44,7 @@ public class GetWeatherTool extends McpTool {
`tools-resources-prompts.md`.
- **Transport**: Streamable HTTP, `POST`-only, stateless in this revision — see `transport.md`
for exactly what that means and why.
-- **Security**: optional, policy-driven OAuth2 via `flash-ext-oidc` — see `security.md`.
+- **Security**: optional, policy-driven OAuth2 via `flash-ext-auth-oidc` — see `security.md`.
- **JSON**: this extension owns its JSON handling independently of `flash-ext-jackson` — see
`jackson-interop.md` for why, and how a future opt-in reuse could work.
diff --git a/flash-extensions/flash-ext-mcp/docs/jackson-interop.md b/flash-extensions/flash-ext-mcp/docs/jackson-interop.md
index dddee59..a968ad9 100644
--- a/flash-extensions/flash-ext-mcp/docs/jackson-interop.md
+++ b/flash-extensions/flash-ext-mcp/docs/jackson-interop.md
@@ -24,7 +24,7 @@ see `tools-resources-prompts.md` — and the fixed `TextContent`/`TextResourceCo
as a `JsonNode` tree, not as a databound class, for the same reason — a JSON-RPC tool call's
arguments aren't a DTO with getters/setters, they're a dynamic, per-tool-defined bag of values.
-This mirrors how `flash-ext-oidc` already handles its own internal JSON needs (`json-smart` for
+This mirrors how `flash-ext-auth-oidc` already handles its own internal JSON needs (`json-smart` for
token-endpoint responses) independently of `flash-ext-jackson` — extensions with protocol-level
JSON needs that are shaped by a spec, not by user code, own that JSON handling themselves rather
than routing it through the app's general-purpose JSON extension.
diff --git a/flash-extensions/flash-ext-mcp/docs/keycloak.md b/flash-extensions/flash-ext-mcp/docs/keycloak.md
index 6326541..e930b77 100644
--- a/flash-extensions/flash-ext-mcp/docs/keycloak.md
+++ b/flash-extensions/flash-ext-mcp/docs/keycloak.md
@@ -1,7 +1,7 @@
# Keycloak cookbook
`security.md` covers the OAuth2 mechanics `McpOidcIntegration` implements against any
-`flash-ext-oidc`-compatible provider. This is the Keycloak-specific setup: the exact Admin
+`flash-ext-auth-oidc`-compatible provider. This is the Keycloak-specific setup: the exact Admin
Console configuration for a working MCP OAuth2 flow with open Dynamic Client Registration
(DCR) — no pre-registered clients, any MCP client self-registers on first connect.
diff --git a/flash-extensions/flash-ext-mcp/docs/security.md b/flash-extensions/flash-ext-mcp/docs/security.md
index 43e3903..412b12e 100644
--- a/flash-extensions/flash-ext-mcp/docs/security.md
+++ b/flash-extensions/flash-ext-mcp/docs/security.md
@@ -7,10 +7,10 @@ Allowed Client Scopes configuration `scopes_supported` needs to actually work.
## `McpSecurity`
-`McpConfig.security(...)` controls how the MCP endpoint reacts to `flash-ext-oidc` being
+`McpConfig.security(...)` controls how the MCP endpoint reacts to `flash-ext-auth-oidc` being
installed (`ctx.find(OidcMiddleware.class)`), resolved once at boot in `McpExtension.routes()`:
-| Policy | `flash-ext-oidc` installed | `flash-ext-oidc` absent |
+| Policy | `flash-ext-auth-oidc` installed | `flash-ext-auth-oidc` absent |
|---|---|---|
| `REQUIRED` | protected | **boot fails** (`IllegalStateException`) |
| `AUTO` (default) | protected | runs unprotected, logs a warning |
@@ -21,18 +21,18 @@ turns "someone forgot to wire up OAuth2" into a startup crash instead of a silen
endpoint. `AUTO` is meant for local development, where spinning up a real identity provider is
friction you don't want yet.
-## Why `flash-ext-oidc` is an *optional* Maven dependency, concretely
+## Why `flash-ext-auth-oidc` is an *optional* Maven dependency, concretely
Maven's `true` only affects **transitive** propagation: consumers of
-`flash-ext-mcp` don't get `flash-ext-oidc` pulled in automatically unless they add it themselves.
-Within `flash-ext-mcp` itself, `flash-ext-oidc`'s classes are on the compile/test classpath as
+`flash-ext-mcp` don't get `flash-ext-auth-oidc` pulled in automatically unless they add it themselves.
+Within `flash-ext-mcp` itself, `flash-ext-auth-oidc`'s classes are on the compile/test classpath as
normal — this extension can (and does) reference `OidcMiddleware`/`ClaimsHolder` directly in
source.
That reference is isolated in its own class, `McpOidcIntegration`, invoked only from inside a
`catch (NoClassDefFoundError)` block. A bare class-literal like `OidcMiddleware.class` (which
`ctx.find(OidcMiddleware.class)` needs) forces the JVM to resolve that type the moment it's
-evaluated — if `flash-ext-oidc` is not on the *runtime* classpath at all (a genuinely
+evaluated — if `flash-ext-auth-oidc` is not on the *runtime* classpath at all (a genuinely
MCP-only install, no OAuth2 anywhere in the app), the first such reference throws
`NoClassDefFoundError`. Keeping that reference inside a separate, lazily-loaded class means
`McpExtension` itself loads and works fine standalone; only the attempt to actually use OIDC
@@ -45,7 +45,7 @@ When oidc is available and `security() != NONE`, `McpOidcIntegration` (an isolat
lazily-loaded bridge — see its javadoc) derives everything an MCP OAuth2 resource server needs
straight from the installed `OidcMiddleware`, with no additional `McpConfig` calls required:
-1. The MCP route is wrapped with `flash-ext-oidc`'s own `OidcMiddleware.protect(resourceMetadataPath)`
+1. The MCP route is wrapped with `flash-ext-auth-oidc`'s own `OidcMiddleware.protect(resourceMetadataPath)`
— the same Bearer-token/JWKS validation path used everywhere else in Flash5, plus a
`resource_metadata` challenge parameter (see below). No JWT parsing or JWKS handling is
reimplemented here.
@@ -114,7 +114,7 @@ unaffected — this parameter is additive and MCP-specific.
## Per-tool `@RolesAllowed`/`@ScopesAllowed`
-`McpTool` subclasses can carry `flash-ext-oidc`'s `@RolesAllowed`/`@ScopesAllowed`:
+`McpTool` subclasses can carry `flash-ext-auth-oidc`'s `@RolesAllowed`/`@ScopesAllowed`:
```java
@Tool(name = "delete_route", description = "Delete a route")
@@ -124,7 +124,7 @@ public class DeleteRouteTool extends McpTool {
}
```
-This does **not** reuse `flash-ext-oidc`'s per-route middleware mechanism (`ctx.addAnnotationProcessor`,
+This does **not** reuse `flash-ext-auth-oidc`'s per-route middleware mechanism (`ctx.addAnnotationProcessor`,
the thing that makes these annotations work on a `RequestHandler`) — it can't: every tool shares
one HTTP route (`POST {rootPath}`), already wrapped by whatever `McpSecurity` resolved above, so
there is no per-tool route to attach a different middleware chain to. Instead,
@@ -156,7 +156,7 @@ at `app.start()`.
## The `HttpException` safety net
-`flash-ext-oidc`'s middleware throws `HttpException.unauthorized()`/`forbidden()` on auth
+`flash-ext-auth-oidc`'s middleware throws `HttpException.unauthorized()`/`forbidden()` on auth
failure. Flash5's core does **not** special-case `HttpException` in the default exception
handler — the out-of-the-box `AbstractRouter` default always returns a generic `500`, regardless
of the thrown exception's embedded status code; only an app that explicitly calls
diff --git a/flash-extensions/flash-ext-mcp/pom.xml b/flash-extensions/flash-ext-mcp/pom.xml
index db7f5ac..b0a873f 100644
--- a/flash-extensions/flash-ext-mcp/pom.xml
+++ b/flash-extensions/flash-ext-mcp/pom.xml
@@ -19,7 +19,7 @@
dev.relism
- flash-ext-oidc
+ flash-ext-auth-oidc
true
diff --git a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpAuthPolicy.java b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpAuthPolicy.java
index 088d35e..6bdd880 100644
--- a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpAuthPolicy.java
+++ b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpAuthPolicy.java
@@ -10,7 +10,7 @@ import java.util.function.Supplier;
*
* {@code check} is a closure, not a raw role/scope list — this is what lets this record (and
* its only caller, {@link McpDispatcher}) stay free of any compile-time reference to a {@code
- * flash-ext-oidc} type, preserving the same classload isolation {@link McpOidcIntegration}'s
+ * flash-ext-auth-oidc} type, preserving the same classload isolation {@link McpOidcIntegration}'s
* javadoc describes for the rest of the OIDC bridge. Only the plain-JDK {@link Supplier}
* signature crosses the boundary; the closure itself, built once inside {@code
* McpOidcIntegration}, is the only place that ever touches {@code OidcUser}/{@code ClaimsHolder}.
diff --git a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpConfig.java b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpConfig.java
index a0538d7..f088b60 100644
--- a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpConfig.java
+++ b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpConfig.java
@@ -91,7 +91,7 @@ public final class McpConfig {
/**
* Canonical URI of this MCP endpoint, used for RFC 8707 audience binding: tokens whose
* {@code aud} claim does not include this value are rejected. Optional — when
- * {@code flash-ext-oidc} is installed, this is auto-derived per request from the
+ * {@code flash-ext-auth-oidc} is installed, this is auto-derived per request from the
* forwarded/{@code Host} headers (same resolution {@code OidcExtension} uses for its own
* redirect URIs) and audience binding is enforced unconditionally. Set this explicitly
* only to override that guess — a reverse proxy that forwards neither
@@ -102,7 +102,7 @@ public final class McpConfig {
/**
* Authorization server issuer URL, published in the RFC 9728 Protected Resource
* Metadata document at {@code /.well-known/oauth-protected-resource{rootPath}}. Optional
- * — when {@code flash-ext-oidc} is installed, this is auto-derived from its configured
+ * — when {@code flash-ext-auth-oidc} is installed, this is auto-derived from its configured
* issuer. Set this explicitly only to override that (e.g. publishing a different issuer
* than the one actually validating tokens).
*/
diff --git a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpExtension.java b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpExtension.java
index baa0dff..3126f99 100644
--- a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpExtension.java
+++ b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpExtension.java
@@ -25,7 +25,7 @@ import java.util.List;
* .build()))
* .start();
*
- * // With flash-ext-oidc as the OAuth2 resource server — zero extra config: issuer, canonical
+ * // With flash-ext-auth-oidc as the OAuth2 resource server — zero extra config: issuer, canonical
* // resource identifier, RFC 8707 audience binding and RFC 9728 metadata are all derived from
* // the installed OidcExtension.
* FlashApp.create(8080)
@@ -83,20 +83,20 @@ public class McpExtension implements FlashExtension {
try {
resolved = McpOidcIntegration.resolve(ctx, config);
} catch (NoClassDefFoundError e) {
- resolved = null; // flash-ext-oidc not on the classpath at all
+ resolved = null; // flash-ext-auth-oidc not on the classpath at all
}
if (resolved != null) return resolved;
if (config.security() == McpSecurity.REQUIRED) {
throw new IllegalStateException(
- "McpSecurity.REQUIRED but flash-ext-oidc is not installed for MCP server \"" + config.name() +
+ "McpSecurity.REQUIRED but flash-ext-auth-oidc is not installed for MCP server \"" + config.name() +
"\" — install an OidcExtension before this McpExtension, or relax security to " +
"McpSecurity.AUTO/NONE if this server is meant to be public.");
}
log.warn("[flash-ext-mcp] MCP server \"{}\" is running WITHOUT OAuth2 protection — " +
- "flash-ext-oidc is not installed and McpSecurity.AUTO degrades to unprotected. " +
- "Install flash-ext-oidc or set McpSecurity.REQUIRED to make this a hard failure instead.",
+ "flash-ext-auth-oidc is not installed and McpSecurity.AUTO degrades to unprotected. " +
+ "Install flash-ext-auth-oidc or set McpSecurity.REQUIRED to make this a hard failure instead.",
config.name());
return null;
}
diff --git a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpJson.java b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpJson.java
index 426fc01..64f3c4e 100644
--- a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpJson.java
+++ b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpJson.java
@@ -19,7 +19,7 @@ import java.nio.charset.StandardCharsets;
*
*
Not wired to {@code flash-ext-jackson} on purpose: the MCP JSON-RPC envelope is internal
* protocol plumbing, not a user-facing serialization concern, so this extension owns its
- * mapper independently — same reasoning {@code flash-ext-oidc} applies to its own JSON needs
+ * mapper independently — same reasoning {@code flash-ext-auth-oidc} applies to its own JSON needs
* (see {@code json-smart} there). See {@code docs/jackson-interop.md} for the full rationale
* and how a future opt-in reuse of a shared {@code ObjectMapper} could work.
*/
diff --git a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpOidcIntegration.java b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpOidcIntegration.java
index c971968..5262bb4 100644
--- a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpOidcIntegration.java
+++ b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpOidcIntegration.java
@@ -20,19 +20,19 @@ import java.util.function.Function;
import java.util.function.Supplier;
/**
- * Lazy, isolated bridge to {@code flash-ext-oidc} and {@code flash-ext-auth-core}.
+ * Lazy, isolated bridge to {@code flash-ext-auth-oidc} and {@code flash-ext-auth-core}.
*
*
References to OIDC types only ever resolve when {@link #resolve}/{@link #compileToolPolicy}
* are actually invoked — never at {@link McpExtension} class-load time — because they live in
* this separate nested class. The caller wraps the invocation in {@code catch
* (NoClassDefFoundError)}, exactly like {@code OidcExtension}'s own lazy bridge to {@code
* flash-ext-openapi}. This is what lets {@code flash-ext-mcp} run standalone (MCP-only, no
- * OAuth2) when {@code flash-ext-oidc} is not even on the classpath. {@link Resolved}/{@link
+ * OAuth2) when {@code flash-ext-auth-oidc} is not even on the classpath. {@link Resolved}/{@link
* McpAuthPolicy} carry only oidc-free types back out ({@link Middleware}, {@link String}, a
* {@link Function}, a {@link Supplier}) so no other class in this package ever has to reference
* an OIDC type.
*
- *
Zero-config by design: when {@code flash-ext-oidc} is installed, everything an MCP OAuth2
+ *
Zero-config by design: when {@code flash-ext-auth-oidc} is installed, everything an MCP OAuth2
* resource server needs — issuer, canonical resource identifier, RFC 8707 audience binding, and
* a spec-compliant {@code WWW-Authenticate} challenge (RFC 9728 §5.1) — is derived straight from
* the installed {@link OidcCredentialSource}, with no additional {@link McpConfig} calls.
@@ -123,7 +123,7 @@ final class McpOidcIntegration {
if (!oidcActive) {
throw new IllegalStateException(
"MCP tool \"" + toolClass.getSimpleName() + "\" declares @Authenticated/@RolesAllowed/" +
- "@ScopesAllowed, but this MCP server has no active OAuth2 protection — flash-ext-oidc " +
+ "@ScopesAllowed, but this MCP server has no active OAuth2 protection — flash-ext-auth-oidc " +
"is not installed for it, or McpSecurity is NONE. These annotations require " +
"McpSecurity.AUTO/REQUIRED with an OidcExtension installed; install one, or remove the " +
"annotation from " + toolClass.getSimpleName() + ".");
diff --git a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpRegistry.java b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpRegistry.java
index 4615862..61105d4 100644
--- a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpRegistry.java
+++ b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpRegistry.java
@@ -175,7 +175,7 @@ final class McpRegistry {
/**
* Isolated the same way {@link McpOidcIntegration#resolve} is — {@code
- * NoClassDefFoundError} here means {@code flash-ext-oidc} genuinely isn't on the runtime
+ * NoClassDefFoundError} here means {@code flash-ext-auth-oidc} genuinely isn't on the runtime
* classpath, in which case a tool couldn't have been compiled against
* {@code @RolesAllowed}/{@code @ScopesAllowed} in the first place, so there's nothing to
* check (and nothing lost: {@code oidcActive} is only ever {@code true} once {@link
diff --git a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpSecurity.java b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpSecurity.java
index 211ea5d..21ee37b 100644
--- a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpSecurity.java
+++ b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpSecurity.java
@@ -2,16 +2,16 @@ package dev.relism.flash.ext.mcp;
/**
* OAuth2 requirement policy for the MCP endpoint, resolved against whether
- * {@code flash-ext-oidc} is installed ({@code ctx.find(OidcMiddleware.class)}).
+ * {@code flash-ext-auth-oidc} is installed ({@code ctx.find(OidcMiddleware.class)}).
*/
public enum McpSecurity {
- /** Fail fast at boot if {@code flash-ext-oidc} is not installed — never expose an unprotected MCP endpoint. */
+ /** Fail fast at boot if {@code flash-ext-auth-oidc} is not installed — never expose an unprotected MCP endpoint. */
REQUIRED,
- /** Protect the endpoint if {@code flash-ext-oidc} is installed; otherwise run unprotected and log a warning. */
+ /** Protect the endpoint if {@code flash-ext-auth-oidc} is installed; otherwise run unprotected and log a warning. */
AUTO,
- /** Never protect the endpoint, even if {@code flash-ext-oidc} is installed elsewhere in the app. */
+ /** Never protect the endpoint, even if {@code flash-ext-auth-oidc} is installed elsewhere in the app. */
NONE
}
diff --git a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpTransportGuards.java b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpTransportGuards.java
index 94ab5a0..af6be7b 100644
--- a/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpTransportGuards.java
+++ b/flash-extensions/flash-ext-mcp/src/main/java/dev/relism/flash/ext/mcp/McpTransportGuards.java
@@ -38,7 +38,7 @@ final class McpTransportGuards {
/**
* Safety net around the whole MCP route: translates {@link HttpException} (thrown by
- * {@link #originGuard} or by {@code flash-ext-oidc}'s middleware) into a proper HTTP status
+ * {@link #originGuard} or by {@code flash-ext-auth-oidc}'s middleware) into a proper HTTP status
* directly, instead of relying on the app's global exception handler — which defaults to a
* generic 500 for every exception type unless the app owner overrides it (see
* {@code AbstractRouter}'s default {@code exceptionHandler}). Keeps the MCP endpoint
diff --git a/flash-extensions/flash-ext-mcp/src/test/java/dev/relism/flash/ext/mcp/FakeOidcProvider.java b/flash-extensions/flash-ext-mcp/src/test/java/dev/relism/flash/ext/mcp/FakeOidcProvider.java
index 8eb5f29..799a7a4 100644
--- a/flash-extensions/flash-ext-mcp/src/test/java/dev/relism/flash/ext/mcp/FakeOidcProvider.java
+++ b/flash-extensions/flash-ext-mcp/src/test/java/dev/relism/flash/ext/mcp/FakeOidcProvider.java
@@ -26,7 +26,7 @@ import java.util.UUID;
/**
* Minimal, self-contained fake OIDC provider for tests: real discovery document, real JWKS
* endpoint, real RS256-signed tokens — no network dependency beyond localhost, no mocking
- * framework. Exercises {@code flash-ext-oidc}'s actual discovery + JWKS + JWT validation path.
+ * framework. Exercises {@code flash-ext-auth-oidc}'s actual discovery + JWKS + JWT validation path.
*/
final class FakeOidcProvider implements AutoCloseable {
diff --git a/flash-extensions/flash-ext-mcp/src/test/java/dev/relism/flash/ext/mcp/McpExtensionSecurityTest.java b/flash-extensions/flash-ext-mcp/src/test/java/dev/relism/flash/ext/mcp/McpExtensionSecurityTest.java
index 111b5ea..4b8f606 100644
--- a/flash-extensions/flash-ext-mcp/src/test/java/dev/relism/flash/ext/mcp/McpExtensionSecurityTest.java
+++ b/flash-extensions/flash-ext-mcp/src/test/java/dev/relism/flash/ext/mcp/McpExtensionSecurityTest.java
@@ -16,7 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
- * Exercises the actual OAuth2 resolution rules against a real {@code flash-ext-oidc}
+ * Exercises the actual OAuth2 resolution rules against a real {@code flash-ext-auth-oidc}
* installation backed by {@link FakeOidcProvider} — real discovery, real JWKS, real RS256
* tokens — plus the fail-fast/degrade behavior when oidc is absent.
*
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/InMemoryOidcSessionStore.java b/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/InMemoryOidcSessionStore.java
deleted file mode 100644
index 3df2afc..0000000
--- a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/InMemoryOidcSessionStore.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package dev.relism.flash.ext.oidc;
-
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Thread-safe in-memory {@link OidcSessionStore}.
- *
- *
Sessions are lost on restart and not shared across instances. For
- * production deployments with multiple nodes or restart-persistence requirements,
- * supply a custom implementation via {@link OidcConfig.Builder#sessionStore}.
- */
-public final class InMemoryOidcSessionStore implements OidcSessionStore {
-
- private final ConcurrentHashMap store = new ConcurrentHashMap<>();
-
- @Override public void save(OidcSession s) { store.put(s.id(), s); }
- @Override public Optional find(String id) { return Optional.ofNullable(store.get(id)); }
- @Override public void delete(String id) { store.remove(id); }
-}
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcSession.java b/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcSession.java
deleted file mode 100644
index 742404b..0000000
--- a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcSession.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package dev.relism.flash.ext.oidc;
-
-import java.time.Instant;
-import java.util.Map;
-
-/**
- * An authenticated user's OIDC session — persisted in {@link OidcSessionStore} and
- * looked up via the {@code oidc_session} cookie on every request.
- *
- * Sessions are immutable; a refreshed access token produces a new instance
- * that replaces the old one in the store (same {@link #id()}).
- */
-public final class OidcSession {
-
- private final String id;
- private final String accessToken;
- private final String idToken;
- private final String refreshToken; // may be null
- private final Instant accessTokenExpiresAt;
- private final Map claims; // decoded from id_token
-
- public OidcSession(String id, String accessToken, String idToken,
- String refreshToken, Instant accessTokenExpiresAt,
- Map claims) {
- this.id = id;
- this.accessToken = accessToken;
- this.idToken = idToken;
- this.refreshToken = refreshToken;
- this.accessTokenExpiresAt = accessTokenExpiresAt;
- this.claims = Map.copyOf(claims);
- }
-
- /**
- * Returns {@code true} if the access token has expired or will expire within
- * the next 30 seconds (eager refresh to avoid mid-request expiry).
- */
- public boolean isAccessTokenExpired() {
- return Instant.now().isAfter(accessTokenExpiresAt.minusSeconds(30));
- }
-
- public String id() { return id; }
- public String accessToken() { return accessToken; }
- public String idToken() { return idToken; }
- public String refreshToken() { return refreshToken; }
- public Instant accessTokenExpiresAt() { return accessTokenExpiresAt; }
- public Map claims() { return claims; }
-}
diff --git a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcSessionStore.java b/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcSessionStore.java
deleted file mode 100644
index b23f816..0000000
--- a/flash-extensions/flash-ext-oidc/src/main/java/dev/relism/flash/ext/oidc/OidcSessionStore.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package dev.relism.flash.ext.oidc;
-
-import java.util.Optional;
-
-/**
- * Backing store for {@link OidcSession} objects. The default implementation is
- * {@link InMemoryOidcSessionStore}; supply a custom one via
- * {@link OidcConfig.Builder#sessionStore(OidcSessionStore)} for Redis, JDBC, etc.
- */
-public interface OidcSessionStore {
- void save(OidcSession session);
- Optional find(String sessionId);
- void delete(String sessionId);
-}
diff --git a/flash-extensions/flash-ext-openapi/README.md b/flash-extensions/flash-ext-openapi/README.md
index b49159c..977c86c 100644
--- a/flash-extensions/flash-ext-openapi/README.md
+++ b/flash-extensions/flash-ext-openapi/README.md
@@ -123,7 +123,7 @@ Merge policy:
## OIDC interop
-When `flash-ext-oidc` is installed, OpenAPI integrates automatically:
+When `flash-ext-auth-oidc` is installed, OpenAPI integrates automatically:
- security scheme under `components.securitySchemes`
- per-operation `security`
diff --git a/flash-extensions/pom.xml b/flash-extensions/pom.xml
index a4f195e..fb4b3fb 100644
--- a/flash-extensions/pom.xml
+++ b/flash-extensions/pom.xml
@@ -17,7 +17,7 @@
flash-ext-jackson
flash-ext-openapi
flash-ext-auth-core
- flash-ext-oidc
+ flash-ext-auth-oidc
flash-ext-routeviewer
flash-ext-view-core
flash-ext-view-jte
diff --git a/pom.xml b/pom.xml
index 452ddc5..14a638e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -106,7 +106,7 @@
dev.relism
- flash-ext-oidc
+ flash-ext-auth-oidc
${project.version}