Skip to main content

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:
  1. Registration: User submits credentials with role-specific data
  2. Password Hashing: Password is hashed using BCrypt
  3. Login: Credentials are validated, JWT token is generated
  4. Request Authorization: JWT token is validated on each request

Registration

Endpoint

POST /api/auth/register

Request Structure

firstName
string
required
User’s first name
lastName
string
required
User’s last name
email
string
required
Valid email address
password
string
required
Password (minimum 6 characters)
role
enum
required
One of: STUDENT, ORGANIZER, ADMIN

Role-Specific Fields

studentID
string
Student ID (e.g., “S2024001”)
major
string
Student’s major
yearOfStudy
integer
Current year of study

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();
}
See Roles & Permissions for details on the Single Table Inheritance pattern.

Response Example

{
  "statusCode": 201,
  "message": "User Registered Successfully",
  "data": null,
  "timestamp": "2024-03-15T10:30:00"
}

Login

Endpoint

POST /api/auth/login

Request Structure

{
  "email": "[email protected]",
  "password": "password123"
}

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:
JwtUtils.java:35-42
public String generateToken(String email) {
    return Jwts.builder()
            .subject(email)
            .issuedAt(new Date(System.currentTimeMillis()))
            .expiration(new Date(System.currentTimeMillis() + expirationInt))
            .signWith(key)
            .compact();
}
subject
string
User’s email address
issuedAt
Date
Token issue timestamp
expiration
Date
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);
}

Token Extraction

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:
JwtUtils.java:56-63
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

1

Store Tokens Securely

Store JWT tokens in memory or secure storage. Avoid localStorage for sensitive applications.
2

Include Token in Headers

Always include the token in the Authorization: Bearer <token> header.
3

Handle Token Expiration

Implement token refresh logic or redirect to login when tokens expire.
4

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

Build docs developers (and LLMs) love