Basic Authentication
Published: November 2025
Authentication is one of the most critical aspects of any web application. In this blog post, I'll walk you through building a secure authentication system using the JAQ Stack - Java, Angular, Jersey, and MongoDB - with JSON Web Tokens (JWT) as the authentication mechanism.
Why JWT Authentication?โ
Before diving into the implementation, let's understand why JWT is an excellent choice for modern web applications:
- Stateless: No need to store session data on the server
- Scalable: Works seamlessly across multiple servers
- Secure: Tokens are cryptographically signed
- Standard: Based on RFC 7519, widely supported
- Flexible: Can contain custom claims and user information
Technology Stackโ
Our authentication system is built with:
- Java 8: Enterprise-grade reliability with proven stability
- Jersey 2.26: JAX-RS reference implementation for RESTful services
- Angular 5: Modern frontend framework with excellent tooling
- MongoDB 3.9.1: NoSQL database perfect for user management
- JJWT 0.10.5: Java library for creating and parsing JWTs
- Weld 3.0.5: CDI implementation for dependency injection
- Tomcat: Servlet container for WAR deployment
Architecture Overviewโ
The authentication system follows a clean, layered architecture:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Angular 5 Frontend (Port 4200) โ
โ - Login Component โ
โ - Registration Component โ
โ - Auth Service โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโ
โ HTTP/REST + JWT
โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโ
โ Jersey REST API (Port 8080) โ
โ - Login Resource (/auth/login) โ
โ - Register Resource (/auth/register)โ
โ - User Resource (/auth/users) โ
โ - JWT Token Service โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ โโโโโโโโ
โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโ
โ MongoDB Database โ
โ - Database: jaqstack โ
โ - Collection: users โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Backend Implementationโ
JWT Token Serviceโ
The heart of our authentication system is the AuthenticationTokenService, which handles JWT creation:
@RequestScoped
public class AuthenticationTokenService implements Serializable {
public String issueToken(String username) {
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
String jws = Jwts.builder()
.setSubject(username)
.signWith(key)
.compact();
return jws;
}
}
Key Points:
- Uses HS256 (HMAC-SHA256) algorithm for signing
- Generates a secure random key for each token
- Sets the username as the subject claim
- Returns a compact JWT string
Authentication Resourceโ
The Login resource class handles authentication endpoints:
@Path("/auth")
@RequestScoped
public class Login {
@Inject
AuthenticationTokenService authenticationTokenService;
@Inject
UserService userService;
@POST
@Path("/login")
@PermitAll
public Response authenticate(UserCredentials userCredentials) {
// Validate credentials against database
User user = userService.validateCredentials(userCredentials);
// Issue JWT token
String token = authenticationTokenService.issueToken(userCredentials.getUsername());
AuthenticationToken authenticationToken = new AuthenticationToken();
authenticationToken.setToken(token);
return Response.ok(authenticationToken).build();
}
@POST
@Path("/register")
public Response registerUser(User user) {
String message = userService.addUser(user);
return Response.ok(message).build();
}
@GET
@Path("/users")
public Response getAllUsers() {
List<User> users = userService.allUsers();
return Response.ok(users).build();
}
}
User Validationโ
The UserService handles user validation logic:
@ApplicationScoped
public class UserService implements Serializable {
@Inject
DataService dataService;
public User validateCredentials(UserCredentials userCredentials) {
User user = findUser(userCredentials);
if (user == null) {
throw new AuthenticationException("Bad credentials.");
}
return user;
}
private User findUser(UserCredentials userCredentials) {
return dataService.findUsernamePassword(userCredentials);
}
}
Security Considerations:
- Throws
AuthenticationExceptionfor invalid credentials - Does not reveal whether username or password is incorrect (security best practice)
- Uses CDI for dependency injection
MongoDB Integrationโ
User data is stored in MongoDB using the DataService:
@ApplicationScoped
public class DataService implements Serializable {
public User findUsernamePassword(UserCredentials userCredentials) {
MongoClient mongoClient = MongoClients.create();
MongoDatabase database = mongoClient.getDatabase("jaqstack");
MongoCollection<Document> collection = database.getCollection("users");
BasicDBObject query = new BasicDBObject("username", userCredentials.getUsername())
.append("password", userCredentials.getPassword());
Document doc = collection.find(query).first();
if (doc == null) {
mongoClient.close();
return null;
}
User user = new User();
user.setUsername(doc.getString("username"));
user.setPassword(doc.getString("password"));
// ... map other fields
mongoClient.close();
return user;
}
public String addUser(User user) {
MongoClient mongoClient = MongoClients.create();
MongoDatabase database = mongoClient.getDatabase("jaqstack");
MongoCollection<Document> collection = database.getCollection("users");
Document doc = new Document("username", user.getUsername())
.append("password", user.getPassword())
.append("firstname", user.getFirstname())
.append("lastname", user.getLastname());
collection.insertOne(doc);
mongoClient.close();
return "User " + user.getFirstname() + " added successfully.";
}
}
Frontend Implementationโ
Authentication Serviceโ
The Angular frontend uses a service to handle authentication:
@Injectable()
export class AuthService {
private baseUrl: string = environment.BACKEND_URL + "/service/auth";
constructor(private http: HttpClient) { }
login(credentials: any): Observable<any> {
return this.http.post(`${this.baseUrl}/login`, credentials);
}
register(user: any): Observable<any> {
return this.http.post(`${this.baseUrl}/register`, user);
}
getUsers(): Observable<any> {
return this.http.get(`${this.baseUrl}/users`);
}
}
Token Storageโ
After successful login, the JWT token should be stored securely:
login(credentials: any): void {
this.authService.login(credentials).subscribe(
(response) => {
// Store token in localStorage or sessionStorage
localStorage.setItem('authToken', response.token);
// Redirect to protected area
},
(error) => {
console.error('Login failed:', error);
}
);
}
Security Best Practicesโ
1. Password Storageโ
Important: In production, never store passwords in plain text! Always use password hashing:
// Production-ready approach (not implemented in this example)
import org.mindrot.jbcrypt.BCrypt;
public String hashPassword(String plainPassword) {
return BCrypt.hashpw(plainPassword, BCrypt.gensalt());
}
public boolean checkPassword(String plainPassword, String hashedPassword) {
return BCrypt.checkpw(plainPassword, hashedPassword);
}
2. JWT Secret Key Managementโ
The current implementation generates a new key for each token. In production:
- Use a fixed, secure secret key stored in configuration
- Rotate keys periodically
- Never commit keys to version control
// Production approach
@ApplicationScoped
public class AuthenticationTokenService {
private static final String SECRET_KEY = System.getenv("JWT_SECRET_KEY");
private static final Key key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());
public String issueToken(String username) {
return Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour
.signWith(key)
.compact();
}
}
3. Token Expirationโ
Always set expiration times on tokens:
Date expiration = new Date(System.currentTimeMillis() + 3600000); // 1 hour
String jws = Jwts.builder()
.setSubject(username)
.setExpiration(expiration)
.signWith(key)
.compact();
4. HTTPS Onlyโ
In production, always use HTTPS to prevent token interception:
- Configure Tomcat for SSL/TLS
- Use reverse proxy (nginx, Apache) with SSL certificates
- Set secure cookie flags if storing tokens in cookies
CDI and Dependency Injectionโ
The application uses CDI (Contexts and Dependency Injection) with Weld:
// beans.xml configuration
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
bean-discovery-mode="all">
</beans>
Benefits:
- Loose coupling between components
- Easy testing with mock objects
- Lifecycle management (@ApplicationScoped, @RequestScoped)
- Interceptor support for cross-cutting concerns
Deployment Architectureโ
WAR Deploymentโ
The application is packaged as a WAR file for deployment to Tomcat:
<packaging>war</packaging>
Build Process:
- Compile Java sources
- Build Angular frontend
- Copy Angular dist to WAR resources
- Package everything into
basicauth.war
Tomcat Configurationโ
The application uses Tomcat's servlet container with:
- Jersey servlet for REST endpoints
- Weld listener for CDI
- CORS filter for cross-origin requests
API Endpointsโ
Authentication Endpointsโ
-
POST
/basicauth/service/auth/login- Authenticates user and returns JWT token
- Request:
{"username":"user", "password":"pass"} - Response:
{"token":"eyJhbGciOiJIUzI1NiJ9..."}
-
POST
/basicauth/service/auth/register- Registers a new user
- Request:
{"username":"user", "password":"pass", "firstname":"John", "lastname":"Doe"} - Response:
"User John added successfully."
-
GET
/basicauth/service/auth/users- Retrieves all users (should be protected in production)
- Response:
[{"username":"user1", ...}, {"username":"user2", ...}]
Frontend Integrationโ
Login Flowโ
- User enters credentials in Angular form
- Frontend sends POST request to
/auth/login - Backend validates credentials against MongoDB
- Backend generates JWT token
- Frontend stores token (localStorage/sessionStorage)
- Frontend includes token in subsequent requests
Protected Routesโ
To protect routes in Angular, implement a route guard:
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(): boolean {
const token = localStorage.getItem('authToken');
if (token) {
return true;
}
// Redirect to login
this.router.navigate(['/login']);
return false;
}
}
Testing the Authentication Systemโ
Manual Testing with cURLโ
Register a User:
curl -X POST http://localhost:8080/basicauth/service/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"testuser","password":"test123","firstname":"Test","lastname":"User"}'
Login:
curl -X POST http://localhost:8080/basicauth/service/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"testuser","password":"test123"}'
Use Token in Requests:
curl -X GET http://localhost:8080/basicauth/service/auth/users \
-H "Authorization: Bearer YOUR_JWT_TOKEN_HERE"
Challenges and Solutionsโ
Challenge 1: CORS Configurationโ
Problem: Angular app on port 4200 couldn't access backend on port 8080.
Solution: Configured CORS filter in web.xml:
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
</filter>
Challenge 2: CDI Discoveryโ
Problem: Weld wasn't discovering CDI beans.
Solution:
- Added
beans.xmlwithbean-discovery-mode="all" - Configured Weld listener in
web.xml - Used proper CDI annotations (@ApplicationScoped, @RequestScoped)
Challenge 3: Jersey Auto-Discoveryโ
Problem: REST endpoints weren't being discovered.
Solution:
- Configured Jersey servlet in
web.xml - Used package scanning for JAX-RS resources
- Ensured proper annotations (@Path, @GET, @POST)
Production Considerationsโ
1. Password Hashingโ
Critical: Implement password hashing before production deployment:
- Use BCrypt or Argon2
- Never store plain text passwords
- Implement password strength requirements
2. Token Validationโ
Implement token validation middleware:
@Provider
public class JwtFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) {
// Extract token from Authorization header
// Validate token signature and expiration
// Set user context
}
}
3. Rate Limitingโ
Implement rate limiting to prevent brute force attacks:
- Limit login attempts per IP
- Implement exponential backoff
- Use Redis for distributed rate limiting
4. Logging and Monitoringโ
- Log authentication attempts (success and failure)
- Monitor for suspicious patterns
- Set up alerts for multiple failed login attempts
Lessons Learnedโ
What Worked Wellโ
- JWT Simplicity: JWT tokens are easy to implement and understand
- CDI Integration: Dependency injection made the code clean and testable
- MongoDB Flexibility: Easy to add new user fields without migrations
- Jersey REST: Clean REST API implementation with JAX-RS
Areas for Improvementโ
- Password Security: Need to implement password hashing
- Token Refresh: Should implement refresh token mechanism
- Role-Based Access: Add role-based authorization
- Token Revocation: Implement token blacklisting for logout
Future Enhancementsโ
- OAuth2 Integration: Support for social login (Google, GitHub, etc.)
- Multi-Factor Authentication: Add 2FA support
- Password Reset: Implement password reset flow with email
- Account Lockout: Lock accounts after multiple failed attempts
- Session Management: Track active sessions and allow logout from all devices
- Audit Logging: Comprehensive audit trail for security events
Conclusionโ
Building a JWT-based authentication system with the JAQ Stack has been a valuable learning experience. The combination of Java, Jersey, Angular, and MongoDB provides a solid foundation for secure authentication.
Key Takeaways:
- JWT provides a stateless, scalable authentication mechanism
- CDI makes the codebase maintainable and testable
- MongoDB's flexibility is perfect for user management
- Security must be considered at every layer
While this implementation provides a good starting point, remember that security is an ongoing process. Always:
- Keep dependencies updated
- Follow security best practices
- Conduct regular security audits
- Stay informed about new vulnerabilities
Resourcesโ
- JWT.io - JWT debugger and information
- JJWT Documentation
- Jersey Documentation
- Angular Security Guide
- OWASP Authentication Cheat Sheet
This blog post was written as part of the Basic Authentication application demonstration. The complete source code is available in the repository.
Built with JAQ Stack - Java 8, Jersey 2.26, Angular 5, MongoDB 3.9.1, JWT (JJWT 0.10.5)
