How to secure an endpoint using Jakarta Security Basic authentication

Here in this article we will try to secure a REST API endpoint using the Jakarta Security Basic Authentication Mechanism.
Test Environment
- OpenJDK 17.0.13
- Eclipse GlassFish 7.0.20
- Apache Maven 3.9.1
- VSCode Editor 1.97.2
Jakarta Security
It is an overarching security API in Jakarta EE that strives to address the security needs of all other API’s in Jakarta EE in a holistic way.
Important Jakarta Security Artifacts
- Authentication Mechanism
- Identity Store
- Permission Store
Authentication Mechanism
This is the process which acts like a controller in MVC pattern where in it collects the credentials and uses model to validate these credentials. It knows about the the environment this caller uses to communicate with the server. For an HTTP(S) protocol based authentication mechanism it knows about URLs to redirect or forward to, or about response headers to send to the client.
Here is the list of provided authentication mechanisms.
- Basic
- Form
- Custom Form
- Open ID Connect (OIDC)
Identify Store
It acts like a model in MVC pattern. The identity contains logic to validate said credentials, and embeds or contacts a database. This “database” contains usernames, along with their credentials and (typically) roles. This entity takes the credentials and provides identity of the end user as an output.
Here is the list of provided identity stores.
- Database
- LDAP
- Custom
Permission Store
A permission store is another kind of model that stores permissions, typically either globally, or per role (role-based permissions). This entity then performs a business / data operation where a query and an identity go in, and a yes/no answer goes out.
Jakarta security also provides support for custom authentication mechanism and identity stores when the provided ones are not sufficient. Both provided and custom ones use the same interfaces, and the system doesn’t distinguish between them.
If you are interested in watching the video. Here is the YouTube video on the same step by step procedure outlined below.
Procedure
Step1: Ensure the following Pre-requisite installed
Ensure that you have JDK, Maven and Glassfish Enterprise application server installed on your workstation.
admin@fedser:~$ javac --version
javac 17.0.13
admin@fedser:~$ mvn --version
Apache Maven 3.9.1 (Red Hat 3.9.1-3)
Download and install the Glassfish Enterprise Application server. Also ensure that your PATH variable is set to the bin directory of the glassfish application server.
admin@fedser:~$ export PATH=$PATH:/home/admin/middleware/stack/glassfish7/bin
Update your glassfish server adminconsole user “admin” password by editing the following file.
admin@fedser:~$ cat /home/admin/middleware/stack/glassfish7/glassfish/domains/password.properties
#AS_ADMIN_PASSWORD=adminadmin
AS_ADMIN_PASSWORD=admin@1234
Ensure that the Glassfish application server is up and running.
admin@fedser:~$ asadmin start-domain
Waiting for domain1 to start .........
Waiting finished after 8,101 ms.
Successfully started the domain : domain1
domain Location: /home/admin/middleware/stack/glassfish7/glassfish/domains/domain1
Log File: /home/admin/middleware/stack/glassfish7/glassfish/domains/domain1/logs/server.log
Admin Port: 4,848
Command start-domain executed successfully.
Validate if you able to access the Admin Console page for the Glassfish Server.
URL: http://localhost:4848/common/index.jsf
Step2: Create a maven jakartaee minimal project
Here we will create a maven archetype project using the CLI with the below mentioned options to generate a minimal jakartaee version 10 project.
admin@fedser:vscodeprojects$ mvn archetype:generate -DarchetypeArtifactId="jakartaee10-minimal" -DarchetypeGroupId="org.eclipse.starter" -DarchetypeVersion="1.1.0" -DgroupId="com.stack" -DartifactId="basicauthdemo"
admin@fedser:vscodeprojects$ cd basicauthdemo/
admin@fedser:vscodeprojects$ rm -rf src/main/java/com/stack/basicauthdemo/HelloRecord.java
admin@fedser:basicauthdemo$ rm -rf src/main/resources
Here is the strucutre of our project.
admin@fedser:basicauthdemo$ tree .
.
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── stack
│ └── basicauthdemo
│ ├── ApplicationConfig.java
│ └── resources
│ └── RestResource.java
└── webapp
Step3: Update ApplicationConfig.java
Here we will update the ApplicationConfig.java class with the entrypoint to our REST resources which is “/rest” and secure it using the Jakarta Basic Authentication Mechanism.
- @BasicAuthenticationMechanismDefinition: This annotation is used to enable basic authentication, a standard HTTP authentication method where the client sends a username and password in the request header, encoded in a base64 format. When used, the annotation instructs the container to create and make available a CDI bean that implements the basic authentication mechanism. This annotation is part of the Jakarta EE Security API, which provides mechanisms for securing web applications and other Jakarta EE components.
- @DeclareRoles: This annotation is used to define the security roles that comprise the security model of the application. This annotation is specified on a class, and it typically would be used to define roles that could be tested (for example, by calling isUserInRole) from within the methods of the annotated class.
admin@fedser:basicauthdemo$ cat src/main/java/com/stack/basicauthdemo/ApplicationConfig.java
/********************************************************************************
* Copyright (c) 10/19/2022 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
* v1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
********************************************************************************/
package com.stack.basicauthdemo;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
import jakarta.annotation.security.DeclareRoles;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.security.enterprise.authentication.mechanism.http.BasicAuthenticationMechanismDefinition;
@ApplicationScoped
@BasicAuthenticationMechanismDefinition(realmName = "basicAuth")
@DeclareRoles({ "user", "caller" })
@ApplicationPath("/rest")
public class ApplicationConfig extends Application {
}
Step4: Create CustomIdentityStore.java
IdentityStore is a mechanism for validating a caller’s credentials and accessing a caller’s identity attributes. Here we implement IdentityStore using CustomIdentityStore which will be picked up automatically by Jakarta Security. Here once the credentials are validated return a result of type CredentialValidationResult which consist of user identity and roles that are assigned to this user from the identity store.
admin@fedser:basicauthdemo$ cat src/main/java/com/stack/basicauthdemo/CustomIdentityStore.java
package com.stack.basicauthdemo;
import static jakarta.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
import java.util.Set;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.security.enterprise.credential.UsernamePasswordCredential;
import jakarta.security.enterprise.identitystore.CredentialValidationResult;
import jakarta.security.enterprise.identitystore.IdentityStore;
/**
* A custom identity store that will be picked up automatically by Jakarta Security.
*
* <p>
* Jakarta Security picks up any enabled CDI bean that implements <code>IdentityStore</code>.
*
* @author Arjan Tijms
*
*/
@ApplicationScoped
public class CustomIdentityStore implements IdentityStore {
public CredentialValidationResult validate(UsernamePasswordCredential usernamePasswordCredential) {
if (usernamePasswordCredential.compareTo("admin", "admin@1234")) {
return new CredentialValidationResult("admin", Set.of("user", "caller"));
}
return INVALID_RESULT;
}
}
Step5: Create RestResource.java
Here we are going to build a rest api resource which is protected by basic authentication. This resources provides with the user identity and the role to be granted access to this resource. Also it provides with the OTP number that is generated.
admin@fedser:basicauthdemo$ cat src/main/java/com/stack/basicauthdemo/resources/RestResource.java
/********************************************************************************
* Copyright (c) 10/19/2022 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
* v1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
********************************************************************************/
package com.stack.basicauthdemo.resources;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.security.enterprise.SecurityContext;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.security.SecureRandom;
@Path("/resource")
@RequestScoped
public class RestResource {
@Inject
private SecurityContext securityContext;
private static final String CHARACTERS = "0123456789";
private static final int OTP_LENGTH = 6;
private SecureRandom random = new SecureRandom();
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getCallerRoleAndOTP() {
StringBuilder otp = new StringBuilder(OTP_LENGTH);
for (int i = 0; i < OTP_LENGTH; i++) {
int index = random.nextInt(CHARACTERS.length());
otp.append(CHARACTERS.charAt(index));
}
return
securityContext.getCallerPrincipal().getName() + " : " +
securityContext.isCallerInRole("user") + " : " +
otp.toString();
}
}
Step6: Create Welcome Page
Let’s define the langing page for our REST API application as shown below.
admin@fedser:basicauthdemo$ cat src/main/webapp/index.html
<html>
<body>
<h2> Basic Authentication Demo </h2>
</body>
</html>
Step7: Update beans.xml file
In the context of Java EE (Enterprise Edition) and specifically CDI (Contexts and Dependency Injection), bean-discovery-mode is an attribute used in the beans.xml file to control how CDI beans are discovered in a Java application. When bean-discovery-mode is set to all, it indicates that all classes within the archive (JAR, WAR, or EAR) should be considered for CDI bean discovery.
admin@fedser:basicauthdemo$ cat src/main/webapp/WEB-INF/beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
bean-discovery-mode="all"
version="4.0">
</beans>
Step8: Create web.xml file
Here we are going to define the resource that we want to protect and define the role that is allowed access to this resource. Also we are defining the langing page (ie. index.html)for our application as shown below.
admin@fedser:basicauthdemo$ cat src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<security-constraint>
<web-resource-collection>
<web-resource-name>protected</web-resource-name>
<url-pattern>/rest/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>user</role-name>
</security-role>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
Step9: Update pom.xml to deploy to Glassfish server
Here we are updating the pom.xml “properties” section to add “glassfish.home” directory where the glassfish server is installed. Also we are updating the plugins section with “org.codehaus.cargo” plugin to carry out automated deployment using the mvn cli to the glassfish server based on configuration properties that are provided. If you are not using this plugin you can generate the war package and deploy it manually to your glassfish server.
admin@fedser:servletdemo$ cat pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.stack</groupId>
<artifactId>servletdemo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>servletdemo</name>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<glassfish.home>/home/admin/middleware/stack/glassfish7</glassfish.home>
</properties>
<dependencies>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>10.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>servletdemo</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven3-plugin</artifactId>
<version>1.10.6</version>
<executions>
<execution>
<id>deploy</id>
<phase>integration-test</phase>
<goals>
<goal>redeploy</goal>
</goals>
</execution>
</executions>
<configuration>
<container>
<containerId>glassfish7x</containerId>
<type>installed</type>
<home>${glassfish.home}</home>
</container>
<configuration>
<type>existing</type>
<home>${glassfish.home}/glassfish/domains</home>
<properties>
<cargo.glassfish.domain.name>domain1</cargo.glassfish.domain.name>
</properties>
</configuration>
</configuration>
</plugin>
</plugins>
</build>
</project>
Step10: Build and Deploy Application
Now it’s time to build, package and deploy the application to glassfish application server as shown below.
admin@fedser:servletdemo$ mvn clean package cargo:deploy
Step11: Validate Application
Once the application is deployed we can validate it using as shown below.
First we can launch our landing page for the application using the below URL.
URL: http://localhost:8080/basicauthdemo/index.html

Now we can try to access our REST API resource using the below URL.
URL: http://localhost:8080/basicauthdemo/rest/resource

Hope you enjoyed reading this article. Thank you..
Leave a Reply
You must be logged in to post a comment.