Wednesday, April 21, 2021

Still using Java: Secured REST Resource with Quarkus Form Based Authentication

Quarkus comes with a lot of authentication mechanisms. Today we are going to implement a simple one with Form-Based Authentication. Quarkus has good documentation but the example for authentication is all over the place. We will create one here just for future reference.

You can visit the documentation here.

A quick description of what we want to do is:

- we have some REST endpoints
- we want to secure the endpoints
- with minimal effort
- username and password saved in the database

These are the basic requirement. 

To begin, check out my previous source code from https://github.com/devilkazuya99/inventory-service

It should have 5 existing endpoints in InventoryResource.java. 

The first thing to do is to enable form-based authentication in the application.properties file.


This will unlock some forbidden dark arts into your system.

Now we need to write some entity class to hold our user's credentials.

        @Entity
        @UserDefinition
        public class AppUser extends PanacheEntity {
        
            @Username
            public String username;
            @Password
            public String password;
            @ManyToMany(cascade = CascadeType.ALL)
            @Roles
            public List<UserRole> roles = new ArrayList<>();;
            
            public static void add(String username, String password, String role) { 
                AppUser user = new AppUser();
                user.username = username;
                user.password = BcryptUtil.bcryptHash(password);
                UserRole userRole = new UserRole();
                userRole.role = role;
                user.roles.add(userRole);
                user.persist();
            }
                
        }
        @Entity
        public class UserRole extends PanacheEntity {
            
            @ManyToMany(mappedBy = "roles")
            public List<AppUser> users;
                
            @RolesValue
            public String role;
        }

Our AppUser has a username and password. It also can have a list of UserRoles.


In listCategory() endpoint in InventoryResource.java, add a @RolesAllowed("user") annotation to the function.


Lastly, add 2 static files, one for login and one for the error page.

The error page is just a dummy page with static content. We will explain more on the login page. 


    <div class="container">
        <section id="content">
            <form action="/j_security_check" method="POST">
                <h1>Login Form</h1>
                <div>
                    <input type="text" placeholder="Username" required="" id="username" name="j_username" />
                </div>
                <div>
                    <input type="password" placeholder="Password" required="" id="password" name="j_password" />
                </div>
                <div>
                    <input type="submit" value="Log in" />
                    <a href="#">Lost your password?</a>
                    <a href="#">Register</a>
                </div>
            </form><!-- form -->

        </section><!-- content -->
    </div><!-- container -->

In short, the login form will POST to '/j_security_check', which is provided by Quarkus dark art. The default username is 'j_username' and the password is 'j_password'. That's all it is.

These are default values that can be modified. For more details see here.

Now, starts the application with this command (that you need to remember for the rest of your life).

mvn compile quarkus:dev

Using a web browser we first browse the product page: http://localhost:8080/inventory/product

We will get an empty array as the response. 

[]

Now we browse the catalog page: http://localhost:8080/inventory/category

This time we got redirected to the login page.

The Quarkus dark art is working so far. We annotated the endpoint previously to only allows user with role "user" to access. It is looking good. 

We forgot one important thing. We don't have any user to login. Let's go back and create some.

We create a Startup class. (you can give it any name)

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

Pay attention to the loadUsers function. The function has a @Observes annotation, which will listen to the Quarkus StartEvent. So every time Quarkus starts up, this function will be triggered. When it happened, we cleared the table, and add 2 dummy users to it.

Now, we go back to the category page, http://localhost:8080/inventory/category, key in the username and password, VoilĂ , we can see the empty JSON array.

[]

This is it, folks. Quarkus Form-Based Authentication example. For the next experiment, we are going to put all the services together, and then try out other authentication methods that will fit our design. So stay tuned.  


The source code for this post can be found: https://github.com/devilkazuya99/inventory-service/tree/form-authentication
(make sure you select the correct branch)

No comments: