Index: backend/src/main/java/medora/controller/AuthController.java
===================================================================
--- backend/src/main/java/medora/controller/AuthController.java	(revision 43e476aafc15990f73b0a6b6411d3aa22acc770a)
+++ backend/src/main/java/medora/controller/AuthController.java	(revision 43e476aafc15990f73b0a6b6411d3aa22acc770a)
@@ -0,0 +1,71 @@
+package medora.controller;
+
+import medora.dto.LoginRequest;
+import medora.service.AuthService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/auth")
+@CrossOrigin(origins = "*", maxAge = 3600)
+public class AuthController {
+
+    private static final Logger logger = LoggerFactory.getLogger(AuthController.class);
+
+    private final AuthService authService;
+
+    public AuthController(AuthService authService) {
+        this.authService = authService;
+    }
+
+    /**
+     * UC002 – User Login
+     * Authenticate user and return JWT token
+     */
+    @PostMapping("/login")
+    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
+        try {
+            if (loginRequest.getUsername() == null || loginRequest.getUsername().isBlank()) {
+                return ResponseEntity.badRequest()
+                        .body(Map.of("error", "Username is required"));
+            }
+            if (loginRequest.getPassword() == null || loginRequest.getPassword().isBlank()) {
+                return ResponseEntity.badRequest()
+                        .body(Map.of("error", "Password is required"));
+            }
+
+            Map<String, Object> response = authService.login(loginRequest.getUsername(), loginRequest.getPassword());
+            return ResponseEntity.ok(response);
+        } catch (RuntimeException e) {
+            logger.error("Login error: {}", e.getMessage());
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
+                    .body(Map.of("error", e.getMessage()));
+        } catch (Exception e) {
+            logger.error("Unexpected error during login: {}", e.getMessage());
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+                    .body(Map.of("error", "Login failed: " + e.getMessage()));
+        }
+    }
+
+    /**
+     * UC003 – User Logout
+     * Client-side logout (token is discarded)
+     */
+    @PostMapping("/logout")
+    public ResponseEntity<?> logout() {
+        return ResponseEntity.ok(Map.of("message", "Logged out successfully"));
+    }
+
+    /**
+     * Health check endpoint
+     */
+    @GetMapping("/health")
+    public ResponseEntity<?> health() {
+        return ResponseEntity.ok(Map.of("status", "healthy"));
+    }
+}
Index: backend/src/main/java/medora/dto/LoginRequest.java
===================================================================
--- backend/src/main/java/medora/dto/LoginRequest.java	(revision 43e476aafc15990f73b0a6b6411d3aa22acc770a)
+++ backend/src/main/java/medora/dto/LoginRequest.java	(revision 43e476aafc15990f73b0a6b6411d3aa22acc770a)
@@ -0,0 +1,29 @@
+package medora.dto;
+
+public class LoginRequest {
+    private String username;
+    private String password;
+
+    public LoginRequest() {}
+
+    public LoginRequest(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}
Index: backend/src/main/java/medora/models/domain/User.java
===================================================================
--- backend/src/main/java/medora/models/domain/User.java	(revision 43e476aafc15990f73b0a6b6411d3aa22acc770a)
+++ backend/src/main/java/medora/models/domain/User.java	(revision 43e476aafc15990f73b0a6b6411d3aa22acc770a)
@@ -0,0 +1,56 @@
+package medora.models.domain;
+
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Entity
+@Table(name = "users")
+public class User {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "user_id")
+    private Long userId;
+
+    @Column(name = "username", nullable = false, unique = true)
+    private String username;
+
+    @Column(name = "password", nullable = false)
+    private String password;
+
+    @Column(name = "role", nullable = false)
+    private String role; // PATIENT, DOCTOR, LAB_TECHNICIAN, ADMIN
+
+    @Column(name = "first_name")
+    private String firstName;
+
+    @Column(name = "last_name")
+    private String lastName;
+
+    @Column(name = "is_active", nullable = false)
+    private Boolean isActive = true;
+
+    // Foreign key to patient (only for PATIENT role)
+    @OneToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "patient_id")
+    private Patient patient;
+
+    // Foreign key to doctor (only for DOCTOR role)
+    @OneToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "doctor_id")
+    private Doctors doctor;
+
+    public User() {}
+
+    public User(String username, String password, String role, String firstName, String lastName) {
+        this.username = username;
+        this.password = password;
+        this.role = role;
+        this.firstName = firstName;
+        this.lastName = lastName;
+        this.isActive = true;
+    }
+}
Index: backend/src/main/java/medora/repository/UserRepository.java
===================================================================
--- backend/src/main/java/medora/repository/UserRepository.java	(revision 43e476aafc15990f73b0a6b6411d3aa22acc770a)
+++ backend/src/main/java/medora/repository/UserRepository.java	(revision 43e476aafc15990f73b0a6b6411d3aa22acc770a)
@@ -0,0 +1,13 @@
+package medora.repository;
+
+import medora.models.domain.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface UserRepository extends JpaRepository<User, Long> {
+    Optional<User> findByUsername(String username);
+    boolean existsByUsername(String username);
+}
Index: backend/src/main/java/medora/service/AuthService.java
===================================================================
--- backend/src/main/java/medora/service/AuthService.java	(revision 43e476aafc15990f73b0a6b6411d3aa22acc770a)
+++ backend/src/main/java/medora/service/AuthService.java	(revision 43e476aafc15990f73b0a6b6411d3aa22acc770a)
@@ -0,0 +1,84 @@
+package medora.service;
+
+import medora.models.domain.User;
+import medora.repository.UserRepository;
+import medora.util.JwtUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+@Service
+public class AuthService {
+
+    private static final Logger logger = LoggerFactory.getLogger(AuthService.class);
+
+    private final UserRepository userRepository;
+    private final JwtUtil jwtUtil;
+
+    public AuthService(UserRepository userRepository, JwtUtil jwtUtil) {
+        this.userRepository = userRepository;
+        this.jwtUtil = jwtUtil;
+    }
+
+    @Transactional(readOnly = true)
+    public Map<String, Object> login(String username, String password) {
+        if (username == null || username.isBlank()) {
+            throw new IllegalArgumentException("Username is required");
+        }
+        if (password == null || password.isBlank()) {
+            throw new IllegalArgumentException("Password is required");
+        }
+
+        Optional<User> userOpt = userRepository.findByUsername(username);
+        if (userOpt.isEmpty()) {
+            logger.warn("Login attempt with non-existent username: {}", username);
+            throw new RuntimeException("Invalid username or password");
+        }
+
+        User user = userOpt.get();
+
+        if (!user.getIsActive()) {
+            logger.warn("Login attempt with inactive user: {}", username);
+            throw new RuntimeException("User account is inactive");
+        }
+
+        // Simple password check (in production, use BCrypt)
+        if (!user.getPassword().equals(password)) {
+            logger.warn("Failed login attempt for user: {}", username);
+            throw new RuntimeException("Invalid username or password");
+        }
+
+        // Generate JWT token with patientId for patients
+        Long patientId = user.getPatient() != null ? user.getPatient().getPatientId() : null;
+        String token = jwtUtil.generateToken(user.getUsername(), user.getRole(), user.getUserId(), patientId);
+
+        // Return response
+        Map<String, Object> response = new HashMap<>();
+        response.put("token", token);
+        response.put("userId", user.getUserId());
+        response.put("patientId", user.getPatient() != null ? user.getPatient().getPatientId() : null);
+        response.put("username", user.getUsername());
+        response.put("role", user.getRole());
+        response.put("firstName", user.getFirstName());
+        response.put("lastName", user.getLastName());
+
+        logger.info("User logged in successfully: {}", username);
+        return response;
+    }
+
+    @Transactional
+    public void createUser(String username, String password, String role, String firstName, String lastName) {
+        if (userRepository.existsByUsername(username)) {
+            throw new RuntimeException("Username already exists");
+        }
+
+        User user = new User(username, password, role, firstName, lastName);
+        userRepository.save(user);
+        logger.info("User created: {} with role: {}", username, role);
+    }
+}
Index: backend/src/main/java/medora/util/JwtUtil.java
===================================================================
--- backend/src/main/java/medora/util/JwtUtil.java	(revision 43e476aafc15990f73b0a6b6411d3aa22acc770a)
+++ backend/src/main/java/medora/util/JwtUtil.java	(revision 43e476aafc15990f73b0a6b6411d3aa22acc770a)
@@ -0,0 +1,34 @@
+package medora.util;
+
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.security.Keys;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.SecretKey;
+import java.util.Date;
+
+@Component
+public class JwtUtil {
+
+    @Value("${jwt.secret:your-secret-key-change-this-in-production}")
+    private String jwtSecret;
+
+    @Value("${jwt.expiration:86400000}")
+    private long jwtExpirationMs;
+
+    public String generateToken(String username, String role, Long userId, Long patientId) {
+        SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes());
+
+        return Jwts.builder()
+                .subject(username)
+                .claim("role", role)
+                .claim("userId", userId)
+                .claim("patientId", patientId)
+                .issuedAt(new Date())
+                .expiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
+                .signWith(key, SignatureAlgorithm.HS256)
+                .compact();
+    }
+}
