diff --git a/quarkus-modules/quarkus-rbac/README.md b/quarkus-modules/quarkus-rbac/README.md
new file mode 100644
index 0000000000..512b64d9b2
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/README.md
@@ -0,0 +1,2 @@
+## Relevant Articles
+
diff --git a/quarkus-modules/quarkus-rbac/pom.xml b/quarkus-modules/quarkus-rbac/pom.xml
new file mode 100644
index 0000000000..28d779b1b0
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/pom.xml
@@ -0,0 +1,177 @@
+
+
+ 4.0.0
+ com.baeldung.quarkus
+ quarkus-rbac
+ 1.0.0-SNAPSHOT
+
+
+ com.baeldung
+ quarkus-modules
+ 1.0.0-SNAPSHOT
+
+
+
+
+
+
+ ${quarkus.platform.group-id}
+ ${quarkus.platform.artifact-id}
+ ${quarkus.platform.version}
+ pom
+ import
+
+
+
+
+
+
+ io.quarkus
+ quarkus-jdbc-h2
+
+
+ io.quarkus
+ quarkus-hibernate-orm
+
+
+ io.quarkus
+ quarkus-hibernate-orm-panache
+
+
+ io.quarkus
+ quarkus-resteasy
+
+
+ io.quarkus
+ quarkus-smallrye-jwt-build
+
+
+ io.quarkus
+ quarkus-smallrye-jwt
+
+
+ io.quarkus
+ quarkus-resteasy-jackson
+
+
+ io.quarkus
+ quarkus-security-jpa
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ io.quarkus
+ quarkus-test-security
+ test
+
+
+ io.quarkus
+ quarkus-test-security-jwt
+ test
+
+
+
+
+
+
+ ${quarkus.platform.group-id}
+ quarkus-maven-plugin
+ ${quarkus.platform.version}
+ true
+
+
+
+ build
+ generate-code
+ generate-code-tests
+
+
+
+
+
+ maven-compiler-plugin
+ ${compiler-plugin.version}
+
+
+ -parameters
+
+
+
+
+ maven-surefire-plugin
+ ${surefire-plugin.version}
+
+
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+
+
+
+
+ native
+
+
+ native
+
+
+
+
+
+ maven-failsafe-plugin
+ ${surefire-plugin.version}
+
+
+
+ integration-test
+ verify
+
+
+
+ ${project.build.directory}/${project.build.finalName}-runner
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+
+
+
+
+ native
+
+
+
+
+
+ 3.12.1
+ false
+ 17
+ UTF-8
+ UTF-8
+ quarkus-bom
+ io.quarkus.platform
+ 3.9.3
+ 3.0.0-M7
+
+
+
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/ApiErrorHandler.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/ApiErrorHandler.java
new file mode 100644
index 0000000000..dc48a01e64
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/ApiErrorHandler.java
@@ -0,0 +1,17 @@
+package com.baeldung.quarkus.rbac.api;
+
+import com.baeldung.quarkus.rbac.users.errors.DomainDataException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+@Provider
+public class ApiErrorHandler implements ExceptionMapper {
+
+ @Override
+ public Response toResponse(final DomainDataException exception) {
+ return Response.status(Response.Status.BAD_REQUEST)
+ .entity(exception.getEntity() == null? exception.getEntity() : new Message(exception.getMessage()))
+ .build();
+ }
+}
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/LoginDto.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/LoginDto.java
new file mode 100644
index 0000000000..7765994791
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/LoginDto.java
@@ -0,0 +1,14 @@
+package com.baeldung.quarkus.rbac.api;
+
+import jakarta.validation.constraints.NotNull;
+
+public record LoginDto(@NotNull String username, @NotNull String password) {
+
+ @Override
+ public String toString() {
+ return "LoginDto{" +
+ "username='" + username + '\'' +
+ ", password='*********'" +
+ '}';
+ }
+}
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/Message.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/Message.java
new file mode 100644
index 0000000000..51bf02dc30
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/Message.java
@@ -0,0 +1,3 @@
+package com.baeldung.quarkus.rbac.api;
+
+public record Message(String message) { }
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/PermissionBasedController.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/PermissionBasedController.java
new file mode 100644
index 0000000000..ece375d4b8
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/PermissionBasedController.java
@@ -0,0 +1,37 @@
+package com.baeldung.quarkus.rbac.api;
+
+import io.quarkus.security.PermissionsAllowed;
+import io.quarkus.security.identity.SecurityIdentity;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/permission-based")
+public class PermissionBasedController {
+
+ private final SecurityIdentity securityIdentity;
+
+ public PermissionBasedController(SecurityIdentity securityIdentity) {
+ this.securityIdentity = securityIdentity;
+ }
+
+ @GET
+ @Path("/resource/version")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @PermissionsAllowed("VIEW_ADMIN_DETAILS")
+ public String get() {
+ return "2.0.0";
+ }
+
+ @GET
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/resource/message")
+ @PermissionsAllowed(value = {"SEND_MESSAGE", "OPERATOR"}, inclusive = true)
+ public Message message() {
+ return new Message("Hello "+securityIdentity.getPrincipal().getName()+"!");
+ }
+}
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/SecureResourceController.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/SecureResourceController.java
new file mode 100644
index 0000000000..81d24d22fc
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/SecureResourceController.java
@@ -0,0 +1,85 @@
+package com.baeldung.quarkus.rbac.api;
+
+import com.baeldung.quarkus.rbac.users.Role;
+import com.baeldung.quarkus.rbac.users.UserService;
+import io.quarkus.security.identity.SecurityIdentity;
+import jakarta.annotation.security.PermitAll;
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.validation.Valid;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+import java.util.stream.Collectors;
+
+@Path("/secured")
+public class SecureResourceController {
+
+ private final UserService userService;
+ private final SecurityIdentity securityIdentity;
+
+ public SecureResourceController(UserService userService, SecurityIdentity securityIdentity) {
+ this.userService = userService;
+ this.securityIdentity = securityIdentity;
+ }
+
+ @GET
+ @Path("/resource")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @RolesAllowed({"VIEW_ADMIN_DETAILS"})
+ public String get() {
+ return "Hello world, here are some details about the admin!";
+ }
+
+ @GET
+ @Path("/resource/user")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @RolesAllowed({"VIEW_USER_DETAILS"})
+ public Message getUser() {
+ return new Message("Hello "+securityIdentity.getPrincipal().getName()+"!");
+ }
+
+ @POST
+ @Path("/resource")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @RolesAllowed("${customer.send.message.permission:SEND_MESSAGE}")
+ public Response getUser(Message message) {
+ return Response.ok(message).build();
+ }
+
+ @POST
+ @Path("/user")
+ @RolesAllowed({"CREATE_USER"})
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response postUser(@Valid final UserDto userDto) {
+
+ final var user = userService.createUser(userDto);
+
+ final var roles = user.getRoles().stream().map(Role::getName).collect(Collectors.toSet());
+
+ return Response.ok(new UserResponse(user.getUsername(), roles)).build();
+ }
+
+ @POST
+ @Path("/login")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @PermitAll
+ public Response login(@Valid final LoginDto loginDto) {
+ if (userService.checkUserCredentials(loginDto.username(), loginDto.password())) {
+ final var user = userService.findByUsername(loginDto.username());
+ final var token = userService.generateJwtToken(user);
+ return Response.ok().entity(new TokenResponse("Bearer " + token,"3600")).build();
+ } else {
+ return Response.status(Response.Status.UNAUTHORIZED).entity(new Message("Invalid credentials")).build();
+ }
+ }
+}
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/TokenResponse.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/TokenResponse.java
new file mode 100644
index 0000000000..8a427b2e2a
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/TokenResponse.java
@@ -0,0 +1,4 @@
+package com.baeldung.quarkus.rbac.api;
+
+public record TokenResponse(String token, String expiresIn){
+}
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/UserDto.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/UserDto.java
new file mode 100644
index 0000000000..a8f6302712
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/UserDto.java
@@ -0,0 +1,9 @@
+package com.baeldung.quarkus.rbac.api;
+
+import io.smallrye.common.constraint.NotNull;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.Size;
+
+import java.util.Set;
+
+public record UserDto(@NotNull String username, @NotNull String password, @Size(min = 1) Set roles, @Email String email) { }
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/UserResponse.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/UserResponse.java
new file mode 100644
index 0000000000..2f10be8d36
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/api/UserResponse.java
@@ -0,0 +1,6 @@
+package com.baeldung.quarkus.rbac.api;
+
+import java.util.Set;
+
+public record UserResponse(String username, Set roles) { }
+
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/Permission.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/Permission.java
new file mode 100644
index 0000000000..9127443685
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/Permission.java
@@ -0,0 +1,10 @@
+package com.baeldung.quarkus.rbac.users;
+
+public enum Permission {
+
+ VIEW_ADMIN_DETAILS,
+ VIEW_USER_DETAILS,
+ SEND_MESSAGE,
+ CREATE_USER
+
+}
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/PermissionConverter.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/PermissionConverter.java
new file mode 100644
index 0000000000..b86a84accb
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/PermissionConverter.java
@@ -0,0 +1,33 @@
+package com.baeldung.quarkus.rbac.users;
+
+import jakarta.persistence.AttributeConverter;
+import jakarta.persistence.Converter;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Converter
+public class PermissionConverter implements AttributeConverter, String> {
+
+ @Override
+ public String convertToDatabaseColumn(Set attribute) {
+ if (attribute == null || attribute.isEmpty()) {
+ return null;
+ }
+ return attribute.stream()
+ .map(Permission::name)
+ .collect(Collectors.joining(","));
+ }
+
+ @Override
+ public Set convertToEntityAttribute(String dbData) {
+ if (dbData == null || dbData.isEmpty()) {
+ return Set.of();
+ }
+ return Arrays.stream(dbData.split(","))
+ .map(String::trim)
+ .map(Permission::valueOf)
+ .collect(Collectors.toSet());
+ }
+}
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/Role.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/Role.java
new file mode 100644
index 0000000000..4177f695ae
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/Role.java
@@ -0,0 +1,39 @@
+package com.baeldung.quarkus.rbac.users;
+
+import io.quarkus.security.jpa.Roles;
+import jakarta.persistence.Convert;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Entity
+@Table(name = "roles")
+public class Role {
+
+ @Id
+ private String name;
+
+ @Roles
+ @Convert(converter = PermissionConverter.class)
+ private Set permissions = new HashSet<>();
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Set getPermissions() {
+ return permissions;
+ }
+
+ public void setPermissions(Set permissions) {
+ this.permissions = permissions;
+ }
+
+}
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/User.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/User.java
new file mode 100644
index 0000000000..2eff427177
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/User.java
@@ -0,0 +1,87 @@
+package com.baeldung.quarkus.rbac.users;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.JoinTable;
+import jakarta.persistence.ManyToMany;
+import jakarta.persistence.Table;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Entity
+@Table(name = "users")
+public class User {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private Long id;
+
+ @Column(unique = true)
+ private String username;
+
+ @Column
+ private String password;
+
+ @Column(unique = true)
+ private String email;
+
+ @ManyToMany(fetch = FetchType.LAZY)
+ @JoinTable(name = "user_roles",
+ joinColumns = @JoinColumn(name = "user_id"),
+ inverseJoinColumns = @JoinColumn(name = "role_name"))
+ private Set roles = new HashSet<>();
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public Set getRoles() {
+ return roles;
+ }
+
+ public void setRoles(Set roles) {
+ this.roles = roles;
+ }
+
+ public void addRole(Role role) {
+ roles.add(role);
+ }
+
+ public void removeRole(Role role) {
+ roles.remove(role);
+ }
+}
\ No newline at end of file
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/UserRepository.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/UserRepository.java
new file mode 100644
index 0000000000..2802b50ba8
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/UserRepository.java
@@ -0,0 +1,20 @@
+package com.baeldung.quarkus.rbac.users;
+
+import io.quarkus.hibernate.orm.panache.PanacheRepository;
+import jakarta.enterprise.context.ApplicationScoped;
+
+import java.util.Collection;
+import java.util.List;
+
+
+@ApplicationScoped
+class UserRepository implements PanacheRepository {
+
+ public List findRoles(final Collection roles) {
+ if (roles == null || roles.isEmpty()) {
+ return List.of();
+ }
+
+ return find("SELECT r FROM Role r WHERE r.name in ?1", roles).project(Role.class).list();
+ }
+}
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/UserService.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/UserService.java
new file mode 100644
index 0000000000..55f44df486
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/UserService.java
@@ -0,0 +1,76 @@
+package com.baeldung.quarkus.rbac.users;
+
+import com.baeldung.quarkus.rbac.api.UserDto;
+import com.baeldung.quarkus.rbac.users.errors.EntityNotFoundException;
+import com.baeldung.quarkus.rbac.users.errors.InvalidRolesProvidedException;
+import io.quarkus.elytron.security.common.BcryptUtil;
+import io.smallrye.jwt.build.Jwt;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.transaction.Transactional;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.jwt.Claims;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@ApplicationScoped
+public class UserService {
+
+ private final UserRepository repository;
+ private final String issuer;
+
+ public UserService(final UserRepository repository,
+ final @ConfigProperty(name = "mp.jwt.verify.issuer") String issuer) {
+ this.repository = repository;
+ this.issuer = issuer;
+ }
+
+ public String generateJwtToken(final User user) {
+
+ final Set permissions = user.getRoles()
+ .stream()
+ .flatMap(role -> role.getPermissions().stream())
+ .map(Permission::name)
+ .collect(Collectors.toSet());
+
+ return Jwt.issuer(issuer)
+ .upn(user.getUsername())
+ .groups(permissions)
+ .expiresIn(3600)
+ .claim(Claims.email_verified.name(), user.getEmail())
+ .sign();
+ }
+
+ @Transactional
+ public User createUser(final UserDto userDto) {
+
+ final var roles = repository.findRoles(userDto.roles());
+
+ if (roles.size() != userDto.roles().size()) {
+ throw new InvalidRolesProvidedException("Unknown role provided");
+ }
+
+ final var user = new User();
+
+ user.setUsername(userDto.username());
+ user.setPassword(BcryptUtil.bcryptHash(userDto.password()));
+ user.setRoles(new HashSet<>(roles));
+ user.setEmail(userDto.email());
+
+ repository.persist(user);
+
+ return user;
+ }
+
+ public boolean checkUserCredentials(String username, String password) {
+ final User user = findByUsername(username);
+ return BcryptUtil.matches(password, user.getPassword());
+ }
+
+ public User findByUsername(String username) {
+ return repository.find("username", username).firstResultOptional()
+ .orElseThrow(() -> new EntityNotFoundException(username));
+ }
+
+}
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/errors/DomainDataException.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/errors/DomainDataException.java
new file mode 100644
index 0000000000..caad071e35
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/errors/DomainDataException.java
@@ -0,0 +1,17 @@
+package com.baeldung.quarkus.rbac.users.errors;
+
+import java.io.Serializable;
+
+public abstract class DomainDataException extends RuntimeException {
+
+ private final Serializable entity;
+
+ protected DomainDataException(final String message, final Serializable entity) {
+ super(message);
+ this.entity = entity;
+ }
+
+ public Serializable getEntity() {
+ return entity;
+ }
+}
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/errors/EntityNotFoundException.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/errors/EntityNotFoundException.java
new file mode 100644
index 0000000000..18e6f28f7c
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/errors/EntityNotFoundException.java
@@ -0,0 +1,8 @@
+package com.baeldung.quarkus.rbac.users.errors;
+
+public class EntityNotFoundException extends RuntimeException {
+
+ public EntityNotFoundException(String id) {
+ super("Entity with id " + id + " not found");
+ }
+}
diff --git a/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/errors/InvalidRolesProvidedException.java b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/errors/InvalidRolesProvidedException.java
new file mode 100644
index 0000000000..b847739027
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/java/com/baeldung/quarkus/rbac/users/errors/InvalidRolesProvidedException.java
@@ -0,0 +1,14 @@
+package com.baeldung.quarkus.rbac.users.errors;
+
+import java.io.Serializable;
+
+public class InvalidRolesProvidedException extends DomainDataException {
+
+ public InvalidRolesProvidedException(String message, Serializable entity) {
+ super(message, entity);
+ }
+
+ public InvalidRolesProvidedException(String message) {
+ super(message, null);
+ }
+}
diff --git a/quarkus-modules/quarkus-rbac/src/main/resources/application.properties b/quarkus-modules/quarkus-rbac/src/main/resources/application.properties
new file mode 100644
index 0000000000..845c45f48e
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/resources/application.properties
@@ -0,0 +1,19 @@
+mp.jwt.verify.publickey.location=publicKey.pem
+quarkus.native.resources.includes=publicKey.pem
+mp.jwt.verify.issuer=my-issuer
+smallrye.jwt.sign.key.location=privateKey.pem
+
+quarkus.datasource.db-kind=h2
+quarkus.datasource.jdbc.url=jdbc:h2:mem:testdb_;DB_CLOSE_DELAY=-1;MODE=MYSQL;DB_CLOSE_ON_EXIT=TRUE;DATABASE_TO_LOWER=TRUE
+quarkus.hibernate-orm.dialect=org.hibernate.dialect.MySQLDialect
+quarkus.hibernate-orm.database.generation=drop-and-create
+quarkus.hibernate-orm.log.sql=true
+quarkus.hibernate-orm.sql-load-script=import.sql
+
+quarkus.http.auth.policy.role-policy1.permissions.VIEW_ADMIN_DETAILS=VIEW_ADMIN_DETAILS
+quarkus.http.auth.policy.role-policy1.permissions.VIEW_USER_DETAILS=VIEW_USER_DETAILS
+quarkus.http.auth.policy.role-policy1.permissions.SEND_MESSAGE=SEND_MESSAGE
+quarkus.http.auth.policy.role-policy1.permissions.CREATE_USER=CREATE_USER
+quarkus.http.auth.policy.role-policy1.permissions.OPERATOR=OPERATOR
+quarkus.http.auth.permission.roles1.paths=/permission-based/*
+quarkus.http.auth.permission.roles1.policy=role-policy1
diff --git a/quarkus-modules/quarkus-rbac/src/main/resources/import.sql b/quarkus-modules/quarkus-rbac/src/main/resources/import.sql
new file mode 100644
index 0000000000..cb0e3adfcd
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/resources/import.sql
@@ -0,0 +1,11 @@
+insert into roles (name, permissions) values ('ADMIN', 'VIEW_ADMIN_DETAILS,CREATE_USER,SEND_MESSAGE,VIEW_USER_DETAILS');
+insert into roles (name, permissions) values ('USER', 'VIEW_USER_DETAILS,SEND_MESSAGE');
+insert into roles (name, permissions) values ('GUEST', 'SEND_MESSAGE');
+
+insert into users (id, username, password, email) values (1, 'admin', '$2a$10$sWfRL1ruggfeebSfeMnToOeeuQTzSFY.khIlS/dzWY6qYOekisccS', 'admin@test.io');
+insert into users (id, username, password, email) values (2, 'user', '$2a$16$6AZvwlL1PCJ7fgoNVBlezOnoB6WkZlmj6mvQPP5/0uWyso8nVOdXm', 'user@test.io');
+insert into users (id, username, password, email) values (3, 'guest', '$2a$16$Sd0wA6le90dUTe3OAUiSZe.FmPL96XLvRYUUbhitF3.dmgF/dLgFm', 'guest@test.io');
+
+insert into user_roles (user_id, role_name) values (1, 'ADMIN');
+insert into user_roles (user_id, role_name) values (2, 'USER');
+insert into user_roles (user_id, role_name) values (3, 'GUEST');
diff --git a/quarkus-modules/quarkus-rbac/src/main/resources/privateKey.pem b/quarkus-modules/quarkus-rbac/src/main/resources/privateKey.pem
new file mode 100644
index 0000000000..82e5e9bb6c
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/resources/privateKey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCWK8UjyoHgPTLa
+PLQJ8SoXLLjpHSjtLxMqmzHnFscqhTVVaDpCRCb6e3Ii/WniQTWw8RA7vf4djz4H
+OzvlfBFNgvUGZHXDwnmGaNVaNzpHYFMEYBhE8VGGiveSkzqeLZI+Y02G6sQAfDtN
+qqzM/l5QX8X34oQFaTBW1r49nftvCpITiwJvWyhkWtXP9RP8sXi1im5Vi3dhupOh
+nelk5n0BfajUYIbfHA6ORzjHRbt7NtBl0L2J+0/FUdHyKs6KMlFGNw8O0Dq88qnM
+uXoLJiewhg9332W3DFMeOveel+//cvDnRsCRtPgd4sXFPHh+UShkso7+DRsChXa6
+oGGQD3GdAgMBAAECggEAAjfTSZwMHwvIXIDZB+yP+pemg4ryt84iMlbofclQV8hv
+6TsI4UGwcbKxFOM5VSYxbNOisb80qasb929gixsyBjsQ8284bhPJR7r0q8h1C+jY
+URA6S4pk8d/LmFakXwG9Tz6YPo3pJziuh48lzkFTk0xW2Dp4SLwtAptZY/+ZXyJ6
+96QXDrZKSSM99Jh9s7a0ST66WoxSS0UC51ak+Keb0KJ1jz4bIJ2C3r4rYlSu4hHB
+Y73GfkWORtQuyUDa9yDOem0/z0nr6pp+pBSXPLHADsqvZiIhxD/O0Xk5I6/zVHB3
+zuoQqLERk0WvA8FXz2o8AYwcQRY2g30eX9kU4uDQAQKBgQDmf7KGImUGitsEPepF
+KH5yLWYWqghHx6wfV+fdbBxoqn9WlwcQ7JbynIiVx8MX8/1lLCCe8v41ypu/eLtP
+iY1ev2IKdrUStvYRSsFigRkuPHUo1ajsGHQd+ucTDf58mn7kRLW1JGMeGxo/t32B
+m96Af6AiPWPEJuVfgGV0iwg+HQKBgQCmyPzL9M2rhYZn1AozRUguvlpmJHU2DpqS
+34Q+7x2Ghf7MgBUhqE0t3FAOxEC7IYBwHmeYOvFR8ZkVRKNF4gbnF9RtLdz0DMEG
+5qsMnvJUSQbNB1yVjUCnDAtElqiFRlQ/k0LgYkjKDY7LfciZl9uJRl0OSYeX/qG2
+tRW09tOpgQKBgBSGkpM3RN/MRayfBtmZvYjVWh3yjkI2GbHA1jj1g6IebLB9SnfL
+WbXJErCj1U+wvoPf5hfBc7m+jRgD3Eo86YXibQyZfY5pFIh9q7Ll5CQl5hj4zc4Y
+b16sFR+xQ1Q9Pcd+BuBWmSz5JOE/qcF869dthgkGhnfVLt/OQzqZluZRAoGAXQ09
+nT0TkmKIvlza5Af/YbTqEpq8mlBDhTYXPlWCD4+qvMWpBII1rSSBtftgcgca9XLB
+MXmRMbqtQeRtg4u7dishZVh1MeP7vbHsNLppUQT9Ol6lFPsd2xUpJDc6BkFat62d
+Xjr3iWNPC9E9nhPPdCNBv7reX7q81obpeXFMXgECgYEAmk2Qlus3OV0tfoNRqNpe
+Mb0teduf2+h3xaI1XDIzPVtZF35ELY/RkAHlmWRT4PCdR0zXDidE67L6XdJyecSt
+FdOUH8z5qUraVVebRFvJqf/oGsXc4+ex1ZKUTbY0wqY1y9E39yvB3MaTmZFuuqk8
+f3cg+fr8aou7pr9SHhJlZCU=
+-----END PRIVATE KEY-----
\ No newline at end of file
diff --git a/quarkus-modules/quarkus-rbac/src/main/resources/publicKey.pem b/quarkus-modules/quarkus-rbac/src/main/resources/publicKey.pem
new file mode 100644
index 0000000000..12a18c1d18
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/main/resources/publicKey.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq
+Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR
+TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e
+UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9
+AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn
+sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x
+nQIDAQAB
+-----END PUBLIC KEY-----
\ No newline at end of file
diff --git a/quarkus-modules/quarkus-rbac/src/test/java/com/baeldung/quarkus/PermissionBasedControllerIntegrationTest.java b/quarkus-modules/quarkus-rbac/src/test/java/com/baeldung/quarkus/PermissionBasedControllerIntegrationTest.java
new file mode 100644
index 0000000000..55322b7613
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/test/java/com/baeldung/quarkus/PermissionBasedControllerIntegrationTest.java
@@ -0,0 +1,57 @@
+package com.baeldung.quarkus;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.security.TestSecurity;
+import io.quarkus.test.security.jwt.Claim;
+import io.quarkus.test.security.jwt.JwtSecurity;
+import io.restassured.http.ContentType;
+import org.junit.jupiter.api.Test;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.equalTo;
+
+@QuarkusTest
+class PermissionBasedControllerIntegrationTest {
+
+ @Test
+ @TestSecurity(user = "admin", roles = "VIEW_ADMIN_DETAILS")
+ @JwtSecurity(claims = {
+ @Claim(key = "email", value = "admin@test.io")
+ })
+ void givenSecureVersionApi_whenUserIsAuthenticated_thenShouldReturnVersion() {
+ given()
+ .contentType(ContentType.JSON)
+ .get("/permission-based/resource/version")
+ .then()
+ .statusCode(200)
+ .body(equalTo("2.0.0"));
+ }
+
+ @Test
+ @TestSecurity(user = "user", roles = "SEND_MESSAGE")
+ @JwtSecurity(claims = {
+ @Claim(key = "email", value = "user@test.io")
+ })
+ void givenSecureMessageApi_whenUserOnlyHasOnePermission_thenShouldNotAllowRequest() {
+ given()
+ .contentType(ContentType.JSON)
+ .get("/permission-based/resource/message")
+ .then()
+ .statusCode(403);
+ }
+
+ @Test
+ @TestSecurity(user = "new-operator", roles = {"SEND_MESSAGE", "OPERATOR"})
+ @JwtSecurity(claims = {
+ @Claim(key = "email", value = "operator@test.io")
+ })
+ void givenSecureMessageApi_whenUserOnlyHasBothPermissions_thenShouldAllowRequest() {
+ given()
+ .contentType(ContentType.JSON)
+ .get("/permission-based/resource/message")
+ .then()
+ .statusCode(200)
+ .body("message", equalTo("Hello new-operator!"));
+ }
+
+}
\ No newline at end of file
diff --git a/quarkus-modules/quarkus-rbac/src/test/java/com/baeldung/quarkus/SecureResourceControllerIntegrationTest.java b/quarkus-modules/quarkus-rbac/src/test/java/com/baeldung/quarkus/SecureResourceControllerIntegrationTest.java
new file mode 100644
index 0000000000..e23be3d37b
--- /dev/null
+++ b/quarkus-modules/quarkus-rbac/src/test/java/com/baeldung/quarkus/SecureResourceControllerIntegrationTest.java
@@ -0,0 +1,87 @@
+package com.baeldung.quarkus;
+
+import com.baeldung.quarkus.rbac.api.LoginDto;
+import com.baeldung.quarkus.rbac.api.Message;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.security.TestSecurity;
+import io.quarkus.test.security.jwt.Claim;
+import io.quarkus.test.security.jwt.JwtSecurity;
+import io.restassured.http.ContentType;
+import io.restassured.mapper.ObjectMapperType;
+import org.junit.jupiter.api.Test;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+
+@QuarkusTest
+class SecureResourceControllerIntegrationTest {
+
+ @Test
+ void givenSecureLoginApi_whenAdminLogsIn_thenShouldReturnOK() {
+ given()
+ .contentType(ContentType.JSON)
+ .body(new LoginDto("admin", "admin"), ObjectMapperType.JACKSON_2)
+ .post("/secured/login")
+ .then()
+ .statusCode(200)
+ .body("token", notNullValue())
+ .body("expiresIn", equalTo("3600"));
+ }
+
+ @Test
+ @TestSecurity(user = "user", roles = "VIEW_USER_DETAILS")
+ @JwtSecurity(claims = {
+ @Claim(key = "email", value = "user@test.io")
+ })
+ void givenSecureUserApi_whenUserIsAuthenticated_thenShouldReturnCustomMessage() {
+ given()
+ .contentType(ContentType.JSON)
+ .get("/secured/resource/user")
+ .then()
+ .statusCode(200)
+ .body("message", equalTo("Hello user!"));;
+ }
+
+ @Test
+ @TestSecurity(user = "user", roles = "VIEW_USER_DETAILS")
+ @JwtSecurity(claims = {
+ @Claim(key = "email", value = "user@test.io")
+ })
+ void givenSecureAdminApi_whenUserTriesToAccessAdminApi_thenShouldNotAllowRequest() {
+ given()
+ .contentType(ContentType.JSON)
+ .get("/secured/resource")
+ .then()
+ .statusCode(403);
+ }
+
+ @Test
+ @TestSecurity(user = "admin", roles = "VIEW_ADMIN_DETAILS")
+ @JwtSecurity(claims = {
+ @Claim(key = "email", value = "admin@test.io")
+ })
+ void givenSecureAdminApi_whenAdminTriesAccessAdminApi_thenShouldAllowRequest() {
+ given()
+ .contentType(ContentType.JSON)
+ .get("/secured/resource")
+ .then()
+ .statusCode(200)
+ .body(equalTo("Hello world, here are some details about the admin!"));
+ }
+
+ @Test
+ @TestSecurity(user = "guest", roles = "SEND_MESSAGE")
+ @JwtSecurity(claims = {
+ @Claim(key = "email", value = "guest@test.io")
+ })
+ void givenSecureGuestApi_whenCallAsGuess_thenShouldReturnOk() {
+ given()
+ .contentType(ContentType.JSON)
+ .body(new Message("Hello Friend!"), ObjectMapperType.JACKSON_2)
+ .post("/secured/resource")
+ .then()
+ .statusCode(200)
+ .body("message", equalTo("Hello Friend!"));
+ }
+}
\ No newline at end of file