GuicedEE MicroProfile JWT
Bridges the MicroProfile JWT Auth specification to Vert.x 5 JWT authentication inside GuicedEE.
Core Concept
The module converts a Vert.x User (authenticated via JWTAuth) into an org.eclipse.microprofile.jwt.JsonWebToken, making all standard and custom JWT claims available through the MP JWT API. Claims can be injected directly using @Claim — no @Inject required.
Architecture
Vert.x JWTAuth → User → VertxJsonWebToken (implements JsonWebToken)
↓
MicroProfileJwtContext (CallScope + ThreadLocal)
↓
MicroProfileJwtModule (Guice bindings)
↓
@Claim("sub") String subject ← injected per-request
Required Flow
- Add
com.guicedee.microprofile:jwtdependency. - Configure JWT authentication via
@JwtAuthOptionsonpackage-info.java(from the auth module). - Use
@Claimto inject claims into your classes:@Claim("sub") private String subject; @Claim("groups") private Set<String> roles; @Claim("exp") private Long expiration; - Access the full token when needed:
@Inject private JsonWebToken jwt; - Configure
module-info.java:module my.app { requires com.guicedee.microprofile.jwt; opens my.app to com.google.guice; }
Key Classes
VertxJsonWebToken
Bridge from Vert.x User to MP JsonWebToken. Extracts claims from the User's principal() and accessToken attribute.
getName()resolves via MP JWT spec:upn→preferred_username→subgetGroups()maps from thegroupsclaimgetAudience()handles both string and array formatsgetClaim(String)returns typed values, convertingJsonArray→Set<String>andJsonObject→Map
MicroProfileJwtContext
Request-scoped holder for the current JsonWebToken:
- Primary: Stores in
CallScopePropertieswhen a CallScope is active (Vert.x HTTP handlers) - Fallback: Uses
ThreadLocalwhen no call scope is active (unit tests, non-HTTP code)
// In a route handler or auth filter:
User user = routingContext.user();
MicroProfileJwtContext.setCurrent(new VertxJsonWebToken(user));
// ... handle request ...
MicroProfileJwtContext.clear();
Or use the convenience method:
MicroProfileJwtPreStartup.setCurrentUser(routingContext.user());
ClaimValueProvider<T>
Guice Provider<T> that resolves a claim from the current JsonWebToken at injection time.
MicroProfileJwtModule
Guice module (auto-discovered via SPI) that:
- Binds
JsonWebToken→MicroProfileJwtContext::getCurrent - Scans
@Claim-annotated fields via ClassGraph - Creates type-specific
@Named("claimName")bindings for each discovered claim
Supported field types:
| Type | Example | Null/absent behavior |
|---|---|---|
| String | @Claim("sub") String subject | null |
| Set<String> | @Claim("groups") Set<String> roles | Set.of() |
| Long / long | @Claim("exp") Long expiration | 0L |
| Integer / int | @Claim("level") Integer level | 0 |
| Boolean / boolean | @Claim("email_verified") Boolean verified | false |
| Optional<String> | @Claim("sub") Optional<String> subject | Optional.empty() |
| Object | @Claim("custom") Object data | null |
MicroProfileJwtPreStartup
Pre-startup hook that validates the JWT auth provider availability. Also provides:
MicroProfileJwtPreStartup.setCurrentUser(User user);
SPI Registrations
The module registers three Guice SPIs so @Claim works without @Inject:
| SPI | Implementation | Purpose |
|---|---|---|
| InjectionPointProvider | ClaimInjectionPointProvider | Marks @Claim as injection point |
| NamedAnnotationProvider | ClaimNamedAnnotationProvider | Converts @Claim("sub") → @Named("sub") |
| BindingAnnotationProvider | ClaimBindingAnnotationProvider | Registers @Claim as binding annotation |
JPMS Module
module com.guicedee.microprofile.jwt {
requires transitive com.guicedee.vertx;
requires transitive com.guicedee.guicedinjection;
requires transitive io.vertx.auth.jwt;
requires transitive microprofile.jwt.auth.api;
requires transitive jakarta.json;
exports com.guicedee.microprofile.jwt;
exports com.guicedee.microprofile.jwt.implementations;
provides IGuiceModule with MicroProfileJwtModule;
provides IGuicePreStartup with MicroProfileJwtPreStartup;
provides InjectionPointProvider with ClaimInjectionPointProvider;
provides NamedAnnotationProvider with ClaimNamedAnnotationProvider;
provides BindingAnnotationProvider with ClaimBindingAnnotationProvider;
}
Keycloak / OIDC Integration
When using Keycloak or any OIDC provider:
- Configure
@JwtAuthOptionsor@OAuth2Optionsfor the provider. - Fetch JWKS from the provider's certs endpoint.
- Filter signing keys only (
use=sig) to avoidRSA-OAEPencryption key errors. - Use
JWTAuthOptions.addJwk(JsonObject)for JWKS JSON keys (notPubSecKeyOptions).
Keycloak JWTs include preferred_username, realm_access, resource_access, email, given_name, family_name as standard claims — all accessible via getClaim().
Startup Flow
IGuiceContext.instance().inject()
└─ MicroProfileJwtPreStartup (validates JWT provider availability)
└─ MicroProfileJwtModule (Guice bindings)
├─ Binds JsonWebToken → MicroProfileJwtContext::getCurrent
├─ Scans @Claim-annotated fields via ClassGraph
└─ Creates per-type @Named("claimName") providers
└─ HTTP Request arrives
├─ Auth handler authenticates → User
├─ MicroProfileJwtContext.setCurrent(new VertxJsonWebToken(user))
├─ @Claim fields resolved from current JWT
└─ MicroProfileJwtContext.clear() at request end
Non-Negotiable Constraints
- Module must
requires com.guicedee.microprofile.jwt;. - Classes with
@Claimfields mustopenstheir package tocom.google.guice. @Claimworks without@Inject(via SPI registration).MicroProfileJwtContext.clear()must be called at end of request to prevent memory leaks.- The Vert.x auth JWT dependency (
io.vertx:vertx-auth-jwt) is required at runtime for token verification. JsonWebTokenbinding resolves fromMicroProfileJwtContext— it isnulloutside a request scope.- Claim name resolution:
@Claim("name")→ value attribute;@Claim(standard = Claims.sub)→ standard enum name.