Quarkus Security

affaan-m/ECC/skills/quarkus-security

by affaan-mef648e01899ba3e8dc6371642deaaf64b4477775No license275K starsListed Oct 9, 2026Updated Oct 9, 2026Repository updated 4 days ago

Quarkus security implementation patterns: JWT and OIDC authentication, @RolesAllowed RBAC and SecurityIdentity checks, Bean Validation and custom validators, parameterized Panache queries, BCrypt password hashing, CORS and security headers, rate limiting, audit logging, Vault or environment-variable secrets, and dependency CVE scanning. Use when adding authentication or authorization, validating input, managing secrets, or hardening a Quarkus application.

AI-generated overview

Guides securing Quarkus applications with JWT/OIDC auth, RBAC, input validation, secrets, and hardening.

What it does
Provides Quarkus security implementation patterns and code examples covering JWT and OIDC authentication, @RolesAllowed and SecurityIdentity authorization, Bean Validation and custom validators, parameterized Panache queries, BCrypt password hashing, CORS, security headers, rate limiting, audit logging, and secrets via Vault or environment variables. It also covers dependency CVE scanning and lists hardening best practices. It is an instructions-only reference and produces no files or scripts.
When to use it
Use when adding authentication or authorization to a Quarkus application, validating user input, managing secrets, or hardening an existing Quarkus service. It also fits reviewing CORS, security headers, rate limiting, audit logging, and dependency CVE scanning.
Requirements
No scripts or bundled assets; the agent needs no tools beyond itself. Applying the examples assumes a Quarkus/Java project with relevant extensions (for example SmallRye JWT, OIDC, Hibernate Validator, Panache, BCrypt) and, for secrets or CVE scanning, access to Vault or a Maven/Gradle build.

Quarkus Security Review

Best practices for securing Quarkus applications with authentication, authorization, and input validation.

When to Activate

  • Adding authentication (JWT, OIDC, Basic Auth)
  • Implementing authorization with @RolesAllowed or SecurityIdentity
  • Validating user input (Bean Validation, custom validators)
  • Configuring CORS or security headers
  • Managing secrets (Vault, environment variables, config sources)
  • Adding rate limiting or brute-force protection
  • Scanning dependencies for CVEs
  • Working with MicroProfile JWT or SmallRye JWT

Authentication

JWT Authentication

java
// Resource protected with JWT@Path("/api/protected")@Authenticatedpublic class ProtectedResource {
  @Inject  JsonWebToken jwt;
  @Inject  SecurityIdentity securityIdentity;
  @GET  public Response getData() {    String username = jwt.getName();    Set<String> roles = jwt.getGroups();    return Response.ok(Map.of(        "username", username,        "roles", roles,        "principal", securityIdentity.getPrincipal().getName()    )).build();  }}

Configuration (application.properties):

properties
mp.jwt.verify.publickey.location=publicKey.pemmp.jwt.verify.issuer=https://auth.example.com
# OIDCquarkus.oidc.auth-server-url=https://auth.example.com/realms/myrealmquarkus.oidc.client-id=backend-servicequarkus.oidc.credentials.secret=${OIDC_SECRET}

Custom Authentication Filter

java
@Provider@Priority(Priorities.AUTHENTICATION)public class CustomAuthFilter implements ContainerRequestFilter {
  @Inject  SecurityIdentity identity;
  @Override  public void filter(ContainerRequestContext requestContext) {    String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
    // Reject immediately if header is absent or malformed    if (authHeader == null || !authHeader.startsWith("Bearer ")) {      requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());      return;    }
    String token = authHeader.substring(7);    if (!validateToken(token)) {      requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());    }  }
  private boolean validateToken(String token) {    // Token validation logic    return true;  }}

Authorization

Role-Based Access Control

java
@Path("/api/admin")@RolesAllowed("ADMIN")public class AdminResource {
  @GET  @Path("/users")  public List<UserDto> listUsers() {    return userService.findAll();  }
  @DELETE  @Path("/users/{id}")  @RolesAllowed({"ADMIN", "SUPER_ADMIN"})  public Response deleteUser(@PathParam("id") Long id) {    userService.delete(id);    return Response.noContent().build();  }}
@Path("/api/users")public class UserResource {
  @Inject  SecurityIdentity securityIdentity;
  @GET  @Path("/{id}")  @RolesAllowed("USER")  public Response getUser(@PathParam("id") Long id) {    // Check ownership    if (!securityIdentity.hasRole("ADMIN") &&        !isOwner(id, securityIdentity.getPrincipal().getName())) {      return Response.status(Response.Status.FORBIDDEN).build();    }    return Response.ok(userService.findById(id)).build();  }
  private boolean isOwner(Long userId, String username) {    return userService.isOwner(userId, username);  }}

Programmatic Security

java
@ApplicationScopedpublic class SecurityService {
  @Inject  SecurityIdentity securityIdentity;
  public boolean canAccessResource(Long resourceId) {    if (securityIdentity.isAnonymous()) {      return false;    }
    if (securityIdentity.hasRole("ADMIN")) {      return true;    }
    String userId = securityIdentity.getPrincipal().getName();    return resourceRepository.isOwner(resourceId, userId);  }}

Input Validation

Bean Validation

java
// BAD: No validation@POSTpublic Response createUser(UserDto dto) {  return Response.ok(userService.create(dto)).build();}
// GOOD: Validated DTOpublic record CreateUserDto(    @NotBlank @Size(max = 100) String name,    @NotBlank @Email String email,    @NotNull @Min(18) @Max(150) Integer age,    @Pattern(regexp = "^\\+?[1-9]\\d{1,14}$") String phone) {}
@POST@Path("/users")public Response createUser(@Valid CreateUserDto dto) {  User user = userService.create(dto);  return Response.status(Response.Status.CREATED).entity(user).build();}

Custom Validators

java
@Target({ElementType.FIELD, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = UsernameValidator.class)public @interface ValidUsername {  String message() default "Invalid username format";  Class<?>[] groups() default {};  Class<? extends Payload>[] payload() default {};}
public class UsernameValidator implements ConstraintValidator<ValidUsername, String> {  @Override  public boolean isValid(String value, ConstraintValidatorContext context) {    if (value == null) return false;    return value.matches("^[a-zA-Z0-9_-]{3,20}$");  }}
// Usagepublic record CreateUserDto(    @ValidUsername String username,    @NotBlank @Email String email) {}

SQL Injection Prevention

Panache Active Record (Safe by Default)

java
// GOOD: Parameterized queries with PanacheList<User> users = User.list("email = ?1 and active = ?2", email, true);
Optional<User> user = User.find("username", username).firstResultOptional();
// GOOD: Named parametersList<User> users = User.list("email = :email and age > :minAge",    Parameters.with("email", email).and("minAge", 18));

Native Queries (Use Parameters)

java
// BAD: String concatenation@Query(value = "SELECT * FROM users WHERE name = '" + name + "'", nativeQuery = true)
// GOOD: Parameterized native query@Entitypublic class User extends PanacheEntity {  public static List<User> findByEmailNative(String email) {    return getEntityManager()        .createNativeQuery("SELECT * FROM users WHERE email = :email", User.class)        .setParameter("email", email)        .getResultList();  }}

Password Hashing

java
@ApplicationScopedpublic class PasswordService {
  public String hash(String plainPassword) {    return BcryptUtil.bcryptHash(plainPassword);  }
  public boolean verify(String plainPassword, String hashedPassword) {    return BcryptUtil.matches(plainPassword, hashedPassword);  }}
// In service@ApplicationScopedpublic class UserService {  @Inject  PasswordService passwordService;
  @Transactional  public User register(CreateUserDto dto) {    String hashedPassword = passwordService.hash(dto.password());    User user = new User();    user.email = dto.email();    user.password = hashedPassword;    user.persist();    return user;  }
  public boolean authenticate(String email, String password) {    return User.find("email", email)        .firstResultOptional()        .map(u -> passwordService.verify(password, u.password))        .orElse(false);  }}

CORS Configuration

properties
# application.propertiesquarkus.http.cors=truequarkus.http.cors.origins=https://app.example.com,https://admin.example.comquarkus.http.cors.methods=GET,POST,PUT,DELETEquarkus.http.cors.headers=accept,authorization,content-type,x-requested-withquarkus.http.cors.exposed-headers=Content-Dispositionquarkus.http.cors.access-control-max-age=24Hquarkus.http.cors.access-control-allow-credentials=true

Secrets Management

properties
# application.properties - NO SECRETS HERE
# Use environment variablesquarkus.datasource.username=${DB_USER}quarkus.datasource.password=${DB_PASSWORD}quarkus.oidc.credentials.secret=${OIDC_CLIENT_SECRET}
# Or use Vaultquarkus.vault.url=https://vault.example.comquarkus.vault.authentication.kubernetes.role=my-role

HashiCorp Vault Integration

java
@ApplicationScopedpublic class SecretService {
  @ConfigProperty(name = "api-key")  String apiKey; // Fetched from Vault
  public String getSecret(String key) {    return ConfigProvider.getConfig().getValue(key, String.class);  }}

Rate Limiting

Security Note: Never use X-Forwarded-For directly — clients can spoof it. Use the actual remote address from the servlet request, or an authenticated identity (API key, JWT subject) when available.

java
@ApplicationScopedpublic class RateLimitFilter implements ContainerRequestFilter {  private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
  @Inject  HttpServletRequest servletRequest;
  @Override  public void filter(ContainerRequestContext requestContext) {    String clientId = getClientIdentifier();    RateLimiter limiter = limiters.computeIfAbsent(clientId,        k -> RateLimiter.create(100.0)); // 100 requests per second
    if (!limiter.tryAcquire()) {      requestContext.abortWith(          Response.status(429)              .entity(Map.of("error", "Too many requests"))              .build()      );    }  }
  private String getClientIdentifier() {    // Use the container-provided remote address (not X-Forwarded-For).    // If behind a trusted proxy, configure quarkus.http.proxy.proxy-address-forwarding=true    // so getRemoteAddr() returns the real client IP.    return servletRequest.getRemoteAddr();  }}

Security Headers

java
@Providerpublic class SecurityHeadersFilter implements ContainerResponseFilter {
  @Override  public void filter(ContainerRequestContext request, ContainerResponseContext response) {    MultivaluedMap<String, Object> headers = response.getHeaders();
    // Prevent clickjacking    headers.putSingle("X-Frame-Options", "DENY");
    // XSS protection    headers.putSingle("X-Content-Type-Options", "nosniff");    headers.putSingle("X-XSS-Protection", "1; mode=block");
    // HSTS    headers.putSingle("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
    // CSP — avoid 'unsafe-inline' for script-src as it negates XSS protection;    // use nonces or hashes instead. 'unsafe-inline' for style-src is acceptable    // when CSS frameworks require it, but prefer nonces where possible.    headers.putSingle("Content-Security-Policy",        "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");  }}

Audit Logging

java
@ApplicationScopedpublic class AuditService {  private static final Logger LOG = Logger.getLogger(AuditService.class);
  @Inject  SecurityIdentity securityIdentity;
  public void logAccess(String resource, String action) {    String user = securityIdentity.isAnonymous()        ? "anonymous"        : securityIdentity.getPrincipal().getName();
    LOG.infof("AUDIT: user=%s action=%s resource=%s timestamp=%s",        user, action, resource, Instant.now());  }}
// Usage in resource@Path("/api/sensitive")public class SensitiveResource {  @Inject  AuditService auditService;
  @GET  @RolesAllowed("ADMIN")  public Response getData() {    auditService.logAccess("sensitive-data", "READ");    return Response.ok(data).build();  }}

Dependency Security Scanning

bash
# Mavenmvn org.owasp:dependency-check-maven:check
# Gradle./gradlew dependencyCheckAnalyze
# Check Quarkus extensionsquarkus extension list --installable

Best Practices

  • Always use HTTPS in production
  • Enable JWT or OIDC for stateless authentication
  • Use @RolesAllowed for declarative authorization
  • Validate all input with Bean Validation
  • Hash passwords with BCrypt (never plaintext)
  • Store secrets in Vault or environment variables
  • Use parameterized queries to prevent SQL injection
  • Add security headers to all responses
  • Implement rate limiting for public endpoints
  • Audit sensitive operations
  • Keep dependencies updated and scan for CVEs
  • Use SecurityIdentity for programmatic checks
  • Set appropriate CORS policies
  • Test authentication and authorization paths

Source and attribution

Source:affaan-m/ECCinskills/quarkus-securityat commitef648e0

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal