Overview
The EMS uses a stateless JWT-based authentication system that supports role-specific user registration and secure token-based access control.
JWT Tokens
Stateless authentication with configurable expiration
Role-Based Registration
Distinct registration flows for Students, Organizers, and Admins
Secure Password Storage
BCrypt password hashing with Spring Security
Token Validation
Per-request validation via JWT filter chain
Architecture
The authentication flow follows a standard JWT pattern:
- Registration: User submits credentials with role-specific data
- Password Hashing: Password is hashed using BCrypt
- Login: Credentials are validated, JWT token is generated
- Request Authorization: JWT token is validated on each request
Registration
Endpoint
Request Structure
Password (minimum 6 characters)
One of: STUDENT, ORGANIZER, ADMIN
Role-Specific Fields
Student ID (e.g., “S2024001”)
Admin department (defaults to YOUTH_UNION)
Public registration creates STANDARD_ADMIN only. SUPER_ADMIN can only be created by existing SUPER_ADMIN.
Implementation
The registration process uses Single Table Inheritance to create role-specific user entities:
AuthServiceImpl.java:34-102
@Override
public Response register(RegisterRequest request) {
if (userRepository.existsByEmail(request.getEmail())) {
throw new BadRequestException("Email already in use.");
}
User user;
switch (request.getRole()) {
case STUDENT -> {
user = Student.builder()
.studentID(request.getStudentID())
.major(request.getMajor())
.yearOfStudy(request.getYearOfStudy())
.email(request.getEmail())
.firstName(request.getFirstName())
.lastName(request.getLastName())
.passwordHash(passwordEncoder.encode(request.getPassword()))
.role(UserRole.STUDENT)
.accountStatus(AccountStatus.ACTIVE)
.build();
}
case ORGANIZER -> {
user = EventOrganizer.builder()
.organizationName(request.getOrganizationName())
.departmentAffiliation(request.getDepartmentAffiliation())
.email(request.getEmail())
// ... other fields
.build();
}
case ADMIN -> {
user = Administrator.builder()
.adminLevel(AdminLevel.STANDARD_ADMIN)
.department(dept)
// ... other fields
.build();
}
}
userRepository.save(user);
return Response.builder()
.statusCode(201)
.message("User Registered Successfully")
.build();
}
Response Example
{
"statusCode": 201,
"message": "User Registered Successfully",
"data": null,
"timestamp": "2024-03-15T10:30:00"
}
Login
Endpoint
Request Structure
Implementation
The login process validates credentials and generates a JWT token:
AuthServiceImpl.java:105-147
@Override
public Response<AuthResponse> login(LoginRequest request) {
// Validate credentials using Spring Security
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getEmail(),
request.getPassword()
)
);
var user = userRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new NotFoundException("User not found"));
// Generate JWT token
var jwtToken = jwtUtils.generateToken(user.getEmail());
AuthResponse authResponse = AuthResponse.builder()
.token(jwtToken)
.email(user.getEmail())
.role(user.getRole().name())
.id(user.getUserID())
.firstName(user.getFirstName())
.lastName(user.getLastName())
.build();
return Response.<AuthResponse>builder()
.statusCode(200)
.message("Login Successful")
.data(authResponse)
.build();
}
Response Example
{
"statusCode": 200,
"message": "Login Successful",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"email": "[email protected]",
"role": "STUDENT",
"id": 123,
"firstName": "John",
"lastName": "Doe"
},
"timestamp": "2024-03-15T10:35:00"
}
Store the JWT token securely on the client side. Include it in the Authorization: Bearer <token> header for all subsequent requests.
JWT Token Generation
Tokens are generated using the JJWT library with HMAC-SHA256 signing:
public String generateToken(String email) {
return Jwts.builder()
.subject(email)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + expirationInt))
.signWith(key)
.compact();
}
Token expiration timestamp (configurable via expirationInt)
JWT Authentication Filter
Every request passes through the JWT authentication filter:
JwtAuthenticationFilter.java:29-60
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
try {
String token = getTokenFromRequest(request);
if (token != null) {
String email = jwtUtils.getUsernameFromToken(token);
if (StringUtils.hasText(email) &&
SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = customUserDetailsService.loadUserByUsername(email);
if (jwtUtils.isTokenValid(token, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
}
} catch (Exception e) {
log.error("Cannot set user authentication: {}", e.getMessage());
}
filterChain.doFilter(request, response);
}
Tokens are extracted from the Authorization header:
JwtAuthenticationFilter.java:62-68
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
Token Validation
Tokens are validated on each request:
public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
return extractClaims(token, Claims::getExpiration).before(new Date());
}
Security Configuration
Password Encoding
BCrypt with Spring Security’s PasswordEncoder
Stateless Sessions
No server-side session storage required
Token Signing
HMAC-SHA256 with configurable secret key
Configurable Expiration
Token lifetime controlled via expirationInt property
Error Handling
{
"statusCode": 400,
"message": "Email already in use.",
"timestamp": "2024-03-15T10:30:00"
}
{
"statusCode": 401,
"message": "Bad credentials",
"timestamp": "2024-03-15T10:30:00"
}
{
"statusCode": 401,
"message": "Token has expired",
"timestamp": "2024-03-15T10:30:00"
}
{
"statusCode": 401,
"message": "Invalid JWT token",
"timestamp": "2024-03-15T10:30:00"
}
Best Practices
Store Tokens Securely
Store JWT tokens in memory or secure storage. Avoid localStorage for sensitive applications.
Include Token in Headers
Always include the token in the Authorization: Bearer <token> header.
Handle Token Expiration
Implement token refresh logic or redirect to login when tokens expire.
Use HTTPS
Always transmit tokens over HTTPS to prevent interception.
Roles & Permissions
Learn about role-based access control
API Architecture
Understand the Response wrapper pattern