Skip to main content

This guide shows you how to secure a rest endpoint using
Jakarta Authentication.

As we delve into securing RESTful web services, let’s first define a basic
scenario that we will be working with throughout this article.

We are constructing a service that will accept an HTTP GET request at
http://localhost:8080/jakartaee-hello-world/rest/hello. In
response to this request, our service will return a JSON payload, as shown in
the following example:

{
  "hello": "world"
}

In this article, we will secure this endpoint.

Over time, this service can be further enhanced and customized to fit more
complex requirements and situations. By starting with this simple setup, we can
get a better grasp of the fundamental steps involved in securing a RESTful web
service using Jakarta EE. Now, let’s proceed with those steps. For those
unfamiliar with RESTful web services, we recommend reading our
previous article.

Set up your development environment:

  • Install a Java Development Kit (JDK). Please make sure you have Java SE 11 or
    higher (we have tested with Java SE 11 and Java SE 17). You can choose any
    vendor distribution of your choice as well as from
    Adoptium.
  • Install an application server that supports Jakarta EE. Download any of the
    Jakarta EE-compatible products.
  • Install Maven 3 or higher.

To install JDK and Maven, we can use the SDKMan. We can
go through the steps mentioned in the how-to
guide.

How to complete this guide

In this getting started guide, you may use Eclipse Starter for Jakarta EE,
finish each step, or skip fundamental setup stages you already know. You can
also begin with an IDE or choose a project structure from well-known
Maven archetypes.

Create a new project with Eclipse Starter for Jakarta EE

To use Eclipse Starter for Jakarta EE, we need to take the following steps:

  1. Navigate to https://start.jakarta.ee. This service will set up all the
    essential dependencies for an application. The current version of the
    Starter only supports Maven. In the future, we may be able to choose between
    Gradle and Maven.
    A screenshot of the form from start.jakarta.ee to generate a Jakarta EE project.
  2. Select the desired version of Jakarta EE from the available options.
    Currently, the options include Jakarta EE 8, Jakarta EE 9.1, and
    Jakarta EE 10. In addition, you may choose the Jakarta EE Platform or one of
    the Jakarta EE profiles (Web, Core).
  3. For this project, we have chosen Jakarta EE 10 Platform, Java SE 11 and
    WildFly as a Runtime.
  4. Once we have selected our desired options, we will click the generate
    button. This will give us the project structure and sample code, which we
    can then build and run.

Let’s explore the code structure

When we unpack the generated code, we will have a structure of an application.
We can open it in our favourite IDE, and then we can just run it from the
command line.

.
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    └── main
        ├── java
        │   └── org
        │       └── eclipse
        │           └── jakarta
        │               └── hello
        │                   ├── Hello.java
        │                   └── HelloWorldResource.java
        └── webapp
            ├── WEB-INF
            │   └── web.xml
            ├── images
            │   └── jakartaee_logo.jpg
            └── index.html

This generated source code comes with an embedded Maven wrapper. So if you want
to use it, make sure you run the following command first for Unix environments
(Linux or Mac):

$ chmod +x mvnw

Since we are using WildFly as a runtime, the following command would run the
application:

$ ./mvnw clean package wildfly:run

On the other hand, if you have Maven installed, you can run the following
command:

$ mvn clean package wildfly:run

For Windows, we don’t need to run chmod command; rather use mvnw.cmd
instead of mvnw.

Now, if you hit the browser with the following URL, you will see the result.

http://localhost:8080/jakartaee-hello-world

We have already covered how to test them out in our previous article, so we’re
skipping it.

Setting up the Jakarta Authentication

The first step in securing your RESTful web service is to authenticate the user
making the request. In Jakarta EE, this can be achieved using
Jakarta Authentication Specification. This
Specification can be used to set up various forms of authentication, including
basic, form-based, digest, and token-based authentication.

Authentication

Let’s consider basic authentication. To set this up, we’d need to configure
your web.xml file with the following:

<security-constraint>
   <web-resource-collection>
      <web-resource-name>Protected REST Services</web-resource-name>
      <url-pattern>/rest/*</url-pattern>
   </web-resource-collection>
   <auth-constraint>
      <role-name>user</role-name>
   </auth-constraint>
</security-constraint>

<login-config>
   <auth-method>BASIC</auth-method>
   <realm-name>ApplicationRealm</realm-name>
</login-config>

<security-role>
   <role-name>user</role-name>
</security-role>

It sets up basic authentication for the application and protects resources
under the /rest path.

Let’s break it down:

  • Security Constraint: This is used to define the access control rules for
    a specific set of web resources.
    • Web Resource Collection: The <web-resource-collection> tag is used to
      group URLs to apply security constraints. In this configuration, the web
      resource collection is named ‘Protected REST Services’, and it applies to
      all URLs under the /rest path, as defined by
      <url-pattern>/rest/*</url-pattern>.
    • Auth Constraint: The <auth-constraint> tag is used to define the
      roles that are allowed to access the URLs defined in the web resource
      collection. Here, only users with the 'user' role are allowed access.
  • Login Config: This tag is used to define the authentication method that
    the server should use. In this case, it’s set to BASIC, which means HTTP
    Basic Authentication will be used. The <realm-name> is set to
    ApplicationRealm, which is the realm used by the server for authentication.
  • Security Role: Finally, the <security-role> tag is used to define roles
    that can be used in the web application. In this case, the role of ‘user’ is
    defined. Note that the names used in the <role-name> tags need to match the
    roles defined in the server or in your application’s security setup.

Authorization

Once authentication is successful, the next step is authorization.
Authorization is the process of determining whether the authenticated entity
has the right to access the desired resources or perform a certain action.

Jakarta EE uses role-based access control (RBAC) for authorization. In RBAC,
access decisions are based on the roles that individual users have as part of
the system. An entity’s roles are verified against the security constraints
defined in the deployment descriptor of the application.

Jakarta EE provides several annotations to secure your applications:

  • @RolesAllowed: indicates which security roles are allowed to invoke the
    specified methods.
  • @DenyAll: specifies that no security roles are allowed to invoke the
    specified methods.
  • @PermitAll: specifies that all security roles are allowed to invoke the
    specified methods.
  • @RunAs: defines the security identity for executing the specified methods.

Now let’s secure our endpoint. To do that, we have just to make the following
change in the HelloWorldResource:

@Path("hello")
public class HelloWorldResource {

   @GET
   @RolesAllowed("user")
   @Produces({ MediaType.APPLICATION_JSON })
   public Hello hello(@QueryParam("name") String name) {
      if ((name == null) || name.trim().isEmpty())  {
         name = "world";
      }

      return new Hello(name);
   }
}

In this article, we are using @RolesAllowed annotation to secure this
endpoint.

If we run the application, our endpoint will be secured. Let’s try it out.

$ curl -v http://localhost:8080/jakartaee-hello-world/rest/hello

This will produce the following results:

* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /jakartaee-hello-world/rest/hello HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.87.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< Expires: 0
< Connection: keep-alive
< WWW-Authenticate: Basic realm="ApplicationRealm"
< Cache-Control: no-cache, no-store, must-revalidate
< Pragma: no-cache
< Content-Type: text/html;charset=UTF-8
< Content-Length: 71
< Date: Wed, 05 Jul 2023 01:18:44 GMT
<
* Connection #0 to host localhost left intact
<html><head><title>Error</title></head><body>Unauthorized</body></html>

As we can see, our curl command receives a 401 Unauthorized response because it
lacks valid authentication credentials for the requested resource.

The next step is to create a credential so that we can access this endpoint
securely.

Defining Users

In WildFly, the application-users.properties and
application-roles.properties files are typically located in the
standalone/configuration directory of your WildFly installation. Since we are
using embedded WildFly, to find this folder, we have to navigate to
target/server/standalone/configuration folder.

application-users.properties is used to define users and their passwords
(hashed), while application-roles.properties is used to map users to roles.

Here’s how to do it:

  1. Navigate to the WildFly bin (/target/server/bin) folder.
  2. Use the add-user.sh (or add-user.bat for Windows) script to create a new
    user.

The script will guide you through the process of adding a new user:

$ ./add-user.sh

Follow the prompts:

What type of user do you wish to add? 
 a) Management User (mgmt-users.properties) 
 b) Application User (application-users.properties)
(a): b

Enter the details of the new user to add.
Using realm 'ApplicationRealm' as discovered from the existing property files.
Username : your_username
Password : your_password
Re-enter Password : your_password
What roles do you want this user to belong to? (Please enter a comma separated list, or leave blank for none)[  ]: your_role

These are the simple steps to define users and roles in WildFly. Restart the
WildFly server to ensure your changes take effect.

However, we have to be sure, this time, we don’t use clean the target folder
while running from maven. Because the maven clean command removes the target
folder altogether, and our user creation will be lost.

So simply run without clean:

./mvnw package wildfly:run

Now let’s run the curl command again:

curl --user username:password http://localhost:8080/jakartaee-hello-world/rest/hello

Replace the username and password you configured on add-user.sh prompt.

If username and password are correct, then we will get the following output.

{
  "hello": "world"
}

Conclusion

Congratulations! You have just learned how to secure a rest endpoint using
Jakarta Authentication. While this is a crucial starting point, real-world
applications typically store user and role data in databases or LDAP servers
rather than in properties files. Be aware that security is multifaceted and
encompasses aspects such as HTTPS utilization and database protection. For a
comprehensive understanding, it’s recommended to refer to the official Jakarta
EE security documentation and explore other relevant resources online.