Search

Suggested keywords:
  • Java
  • Docker
  • Git
  • React
  • NextJs
  • Spring boot
  • Laravel

Quarkus - Authentication and Authorization With Persistence

  • Share this:

post-title

In this article, We will discuss about Jakarta persistence of the user in MySQL and use those users for authentication and authorization. To know about the basic of the Quarkus microservice framework and getting started, please refer to the previous article.  

Pre-requisties 

MySQL is a open source relational database management system, please refer the installation in the official guide. After installing, start the mysql service. create database security_jpa as shown below. Tables will be created automatically with hibernate autogenerated ddl. 

create database security_jpa ;
show databases;

Building Persistence

Hibernate ORM is the de facto Jakarta Persistence (formerly known as JPA) implementation and offers you the full breadth of an Object Relational Mapper. Hibernate ORM with Panache focuses on making your entities trivial and fun to write in Quarkus.

Lets add the following extensions to the pom.xml. We are going to develop on the existing quarkus getting started code-with-quarkaus project. Security JPA is for persistence and security. Jdbc mysql is the mysql driver and hibernate orm is for the hibernate object relationship management framework for persisting the user entity.

 <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-security-jpa</artifactId>
  </dependency>
  <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-jdbc-mysql</artifactId>
  </dependency>
    <!-- Hibernate ORM specific dependencies -->
  <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-hibernate-orm-panache</artifactId>
  </dependency>

PanacheEntity:

Represents an entity with a generated ID field id of type Long . If your Hibernate entities extend this class they get the ID field and auto-generated accessors to all their public fields. It is annotated with @Entity for referring it as DB entity.  Essential fields username, password and roles are added to this class with annotation @Username, @Password and @Roles. This annotations are referred in security jpa for username and password for authentication. Likewise for authorization, it takes the Roles anntoation. It has the add method for adding the users to database. This method is marked as @Transactional, in case if you want the database transaction managed in service level, move this @Transactional annotation to the service class.

package org.acme.security.jpa;

import io.quarkus.hibernate.orm.panache.PanacheEntity;
import io.smallrye.common.annotation.Identifier;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;

import io.quarkus.elytron.security.common.BcryptUtil;
import io.quarkus.security.jpa.Password;
import io.quarkus.security.jpa.Roles;
import io.quarkus.security.jpa.UserDefinition;
import io.quarkus.security.jpa.Username;
import jakarta.transaction.Transactional;

@Entity
@Table(name = "test_user")
@UserDefinition
@Identifier("externally-defined")
public class EntityUsers extends PanacheEntity {
    @Username
    public String username;
    @Password
    public String password;
    @Roles
    public String role;

    /**
     * Adds a new user to the database
     * @param username the username
     * @param password the unencrypted password (it will be encrypted with bcrypt)
     * @param role the comma-separated roles
     */
    @Transactional
    public static void add(String username, String password, String role) {
        EntityUsers user = new EntityUsers();
        user.username = username;
        user.password = BcryptUtil.bcryptHash(password);
        user.role = role;
        user.persist();
    }
}

 

Below Startup class will create the users at the startup. This method will be called at the server startup using StartupEvent observe method. 

package org.acme.security.jpa;

import jakarta.enterprise.event.Observes;
import jakarta.inject.Singleton;
import jakarta.transaction.Transactional;

import io.quarkus.runtime.StartupEvent;


@Singleton
public class Startup {
    public void loadUsers(@Observes StartupEvent evt) {
        // reset and load all test users
//        User.deleteAll();
        EntityUsers.add("admin", "admin", "admin");
        EntityUsers.add("user", "user", "user");
    }
}

 

Add the below properties for connecting to the mysql and creating the tables. It also enables the security jpa.

quarkus.http.auth.basic=true

quarkus.datasource.db-kind=mysql
quarkus.datasource.username=root
quarkus.datasource.password=rtroot
quarkus.datasource.jdbc.url=jdbc:mysql://localhost:3306/security_jpa

quarkus.hibernate-orm.database.generation=drop-and-create

Now start the quarkaus dev and go the mysql shell prompt to see both the users getting created in security_jpa database.

mysql> show tables;
+------------------------+
| Tables_in_security_jpa |
+------------------------+
| test_user              |
| test_user_SEQ          |
+------------------------+
2 rows in set (0.00 sec)

mysql> select * from test_user;
+----+--------------------------------------------------------------+-------+----------+
| id | password                                                     | role  | username |
+----+--------------------------------------------------------------+-------+----------+
|  1 | $2a$10$PY8Q5sJKqIvMS6g66XsbMuGB.kM2uv/A99X.7QgrHPXWQ8HB2ra7i | admin | admin    |
|  2 | $2a$10$L65UE3t.02y5zkkqgDDLcOiMJ3oeEILHjc3.7.OIsbwisUpIneBb2 | user  | user     |
+----+--------------------------------------------------------------+-------+----------+
2 rows in set (0.00 sec)

 

User Management API

Let's start building user management API. DB model extends Panache entity which will come with findall users to get the users list. It also filter the user method. AdminResource class will have two REST functions.

GET method - If filter by username, returns the particular user information otherwise returns list of users.

POST method - To create the new user. 

package org.acme.rest;

import io.quarkus.runtime.util.StringUtil;
import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism;
import jakarta.annotation.security.RolesAllowed;
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 org.acme.security.jpa.EntityUsers;
import org.jboss.resteasy.reactive.*;

import java.util.List;

import static io.quarkus.arc.ComponentsProvider.LOG;

@Path("/admin")
public class AdminResource {

    @GET
    @RolesAllowed("admin")
    @Produces(MediaType.APPLICATION_JSON)
    public List<EntityUsers> getAllUsers(@RestPath String path,
                                           @RestMatrix String variant,
                                           @RestQuery String username,
                                           @RestHeader("Authorization") String authorization) {

        LOG.infof("Request path %s query parameters - user %s authorization header %s", 
                                path, username, authorization);

        List<EntityUsers> entityUsersList = null;
        HttpAuthenticationMechanism t;
        if (StringUtil.isNullOrEmpty(username))
            entityUsersList = EntityUsers.findAll().list();
        else
            entityUsersList = EntityUsers.find("username",username).list();

        return entityUsersList;
    }

    @POST
    @RolesAllowed("admin")
    @Produces(MediaType.APPLICATION_JSON)
    public List<EntityUsers> createNewUser(@RestPath String path,
                                           @RestMatrix String variant,
                                           @RestForm String username,
                                           @RestForm String password,
                                           @RestForm String role,
                                           @RestHeader("Authorization") String authorization) {

        LOG.infof("Request path %s query parameters - user %s authorization header %s", 
                                        path, username, authorization);

        EntityUsers.add(username, password, role);
        List<EntityUsers> entityUsersList = null;
        entityUsersList = EntityUsers.findAll().list();

        return entityUsersList;
    }
}

 

If we call this method, it will fail for authorization, as the rest api methods are protected by the admin roles.

 curl http://localhost:8080/api/admin -v 
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /api/admin HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 401 Unauthorized
< www-authenticate: basic
< content-length: 0
< 
* Connection #0 to host localhost left intact

We need to invoke the API using admin username and password to get the response. 

//for displaying the users
curl -u admin:admin http://localhost:8080/api/admin
[{"id":1,"username":"admin","password":"$2a$10$SRbIvsLkOYvyKw/XxT3b3OHuPlNk0jdnvdiNCLpR8W.lM7BBsUmXu","role":"admin"},
{"id":2,"username":"user","password":"$2a$10$k3dEfDfd0oXDhiW2nsd/euVdcOHyg7SUteJHV6CR5G5Ml4iHiFhgq","role":"user"}]

//for filtering the users
curl -u admin:admin http://localhost:8080/api/admin?username=admin  -i
HTTP/1.1 200 OK
content-length: 118
Content-Type: application/json;charset=UTF-8

[{"id":1,"username":"admin","password":"$2a$10$SRbIvsLkOYvyKw/XxT3b3OHuPlNk0jdnvdiNCLpR8W.lM7BBsUmXu","role":"admin"}]

//for creating new user
curl -u admin:admin http://localhost:8080/api/admin  -d "username=savithri&password=pass123&role=user"
[{"id":1,"username":"admin","password":"$2a$10$SRbIvsLkOYvyKw/XxT3b3OHuPlNk0jdnvdiNCLpR8W.lM7BBsUmXu","role":"admin"},
{"id":2,"username":"user","password":"$2a$10$k3dEfDfd0oXDhiW2nsd/euVdcOHyg7SUteJHV6CR5G5Ml4iHiFhgq","role":"user"},
{"id":3,"username":"savithri","password":"$2a$10$WJmbqq4giPokLKDgkdyTO.ZKvyiFDcr7/6Wc5/TPPBONAMGn0Tkti","role":"user"}]

Quarkus security architecture works with built in HttpAuthenticationMechanism. It extracts the basic authentication passed in the HTTP header and maps them to SecurityIdentity using IdentityProvider. Identity provider injects security identity to the authenticated resource like UserResource. For detailed understanding of security architecture of quarkus, please refer here

Now if we hit the users /me api with authentication, it will return the username. (This is not happening before ). As the basic authentication will populate the security context to get the username. 

curl -u admin:admin http://localhost:8080/api/users/me 
logged in usernameadmin

We can get the current logged in user from security context user principal and return it. Below codes does that. 

@Path("/users")
public class UserResource {

    @GET
    @Path("/me")
    @Produces(MediaType.TEXT_PLAIN)
    public String currentUser( @Context SecurityContext securityContext) {
        String result = "logged in username" + (String.valueOf(securityContext.getUserPrincipal() != null?
                securityContext.getUserPrincipal().getName(): ""));
        return result;
    }
}

 

Source code is available in Github.

 

 

 

DevGroves Technologies

About author
DevGroves Technologies is a IT consulting and services start-up company which is predominately to web technologies catering to static website, workflow based CRM websites, e-commerce websites and reporting websites tailoring to the customer needs. We also support open source community by writing blogs about how, why and where it need to be used for.