MicroProfile JWT Authentication
JWT bearer auth with @LoginConfig, claim injection, and role-based access.
# CLAUDE.md — MicroProfile JWT Authentication
## Setup
- Add the MP-JWT spec to your runtime (Quarkus: `quarkus-smallrye-jwt`; Open Liberty: bundled).
- On the application class:
```java
@LoginConfig(authMethod = "MP-JWT", realmName = "myapp")
@ApplicationPath("/")
public class App extends Application {}
```
- Configure the public key for verification:
```
mp.jwt.verify.publickey.location=https://auth.example.com/.well-known/jwks.json
mp.jwt.verify.issuer=https://auth.example.com
mp.jwt.verify.audiences=myapp
```
## Securing endpoints
- Use Jakarta Security annotations:
```java
@GET @RolesAllowed("user")
public List<Order> myOrders() { ... }
@POST @RolesAllowed("admin")
public void deleteUser(@PathParam("id") String id) { ... }
```
- `@PermitAll` on public endpoints — be explicit so future developers don't break it accidentally.
- `@DenyAll` on placeholder methods you haven't authorized yet.
## Reading claims
- Inject the full token: `@Inject JsonWebToken jwt`.
- Inject specific claims:
```java
@Inject @Claim("email") String email;
@Inject @Claim("groups") Set<String> groups;
@Inject @Claim("sub") String userId;
```
- Standard claims: `iss`, `sub`, `aud`, `exp`, `iat`, `groups`. Custom claims work the same way.
## Roles
- The `groups` claim is mapped to roles by default. Configure the claim name if your provider uses a different name:
```
mp.jwt.claim.roles.path=resource_access.myapp.roles
```
- Don't trust the token blindly. Validate signature, issuer, audience, expiry. The library does this; just configure correctly.
## Token issuance
- MP-JWT specifies the **verification** side — issuance is up to your auth provider (Keycloak, Auth0, Okta, AWS Cognito).
- For dev, use **smallrye-jwt-build** to issue tokens locally. Never use it in production.
- Pin the JWKS URL to a fixed location — don't accept arbitrary issuers.
## Token transport
- Standard `Authorization: Bearer <token>` header.
- For browser apps, store in HttpOnly Secure cookies. The MP-JWT extension can read from cookies if configured:
```
mp.jwt.token.header=Cookie
mp.jwt.token.cookie=jwt
```
- Never store JWTs in `localStorage`.
## Refresh
- MP-JWT doesn't define refresh. Implement at the auth provider — clients send refresh tokens to the auth server, get fresh access tokens.
- Keep access tokens short-lived (15 min). Refresh tokens longer-lived but revocable server-side.
## Don't
- Don't put sensitive data (PII, plan info) in JWT claims. The token is base64url-decodable by anyone.
- Don't accept tokens without verifying the signature.
- Don't use `@RolesAllowed` and ignore object-level authorization. JWT proves identity; ownership checks happen in the service.
- Don't issue long-lived (months) access tokens to "avoid" refresh complexity.
Other Java templates
Modern Java Rules
Java 21+ defaults: records, sealed types, pattern matching, virtual threads, var.
Java Testing with JUnit 5
JUnit 5 + AssertJ + Mockito + Testcontainers — opinionated test conventions.
Java Virtual Threads & Concurrency
Project Loom virtual threads, structured concurrency, and modern concurrent patterns.
Java Build (Maven & Gradle)
Build conventions, dependency management, multi-module projects, and CI patterns.
Java Streams & Collections
Streams API, immutable collections, Collectors, and idiomatic data manipulation.