Index: pom.xml
===================================================================
--- pom.xml	(revision c87db71f06a0fe440ecc644244f411921061afd0)
+++ pom.xml	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 	<modelVersion>4.0.0</modelVersion>
 	<parent>
@@ -83,5 +84,30 @@
 			<scope>test</scope>
 		</dependency>
-	</dependencies>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context-support</artifactId>
+            <version>6.1.10</version>
+        </dependency>
+
+		<!-- JWT (JJWT) -->
+		<dependency>
+			<groupId>io.jsonwebtoken</groupId>
+			<artifactId>jjwt-api</artifactId>
+			<version>0.12.5</version>
+		</dependency>
+		<dependency>
+			<groupId>io.jsonwebtoken</groupId>
+			<artifactId>jjwt-impl</artifactId>
+			<version>0.12.5</version>
+			<scope>runtime</scope>
+		</dependency>
+		<dependency>
+			<groupId>io.jsonwebtoken</groupId>
+			<artifactId>jjwt-jackson</artifactId>
+			<version>0.12.5</version>
+			<scope>runtime</scope>
+		</dependency>
+
+    </dependencies>
 
 	<build>
Index: src/main/java/mk/ukim/finki/synergymed/SynergyMedApplication.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/SynergyMedApplication.java	(revision c87db71f06a0fe440ecc644244f411921061afd0)
+++ src/main/java/mk/ukim/finki/synergymed/SynergyMedApplication.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -3,4 +3,9 @@
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
 
 @SpringBootApplication
@@ -11,3 +16,12 @@
 	}
 
+	@Bean
+	public PasswordEncoder passwordEncoder() {
+		return new BCryptPasswordEncoder();
+	}
+
+	@Bean
+	public AuthenticationManager authenticationManager(AuthenticationConfiguration cfg) throws Exception {
+		return cfg.getAuthenticationManager();
+	}
 }
Index: src/main/java/mk/ukim/finki/synergymed/config/ConfigProbe.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/config/ConfigProbe.java	(revision 103984275890b6224c562adab88e9c76e8333980)
+++ src/main/java/mk/ukim/finki/synergymed/config/ConfigProbe.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -0,0 +1,12 @@
+package mk.ukim.finki.synergymed.config;
+
+import org.springframework.stereotype.Component;
+
+@Component
+class ConfigProbe {
+    ConfigProbe(org.springframework.core.env.Environment env) {
+        String present = env.containsProperty("security.jwt.secret") ? "YES" : "NO";
+        System.out.println(">> security.jwt.secret present? " + present);
+    }
+}
+
Index: src/main/java/mk/ukim/finki/synergymed/config/SecurityConfig.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/config/SecurityConfig.java	(revision 103984275890b6224c562adab88e9c76e8333980)
+++ src/main/java/mk/ukim/finki/synergymed/config/SecurityConfig.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -0,0 +1,34 @@
+package mk.ukim.finki.synergymed.config;
+
+import lombok.RequiredArgsConstructor;
+import mk.ukim.finki.synergymed.security.JwtAuthFilter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@Configuration
+@EnableMethodSecurity
+@RequiredArgsConstructor
+public class SecurityConfig {
+
+    private final JwtAuthFilter jwtAuthFilter;  // your filter
+
+    @Bean
+    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+        http
+                .csrf(AbstractHttpConfigurer::disable)
+                .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+                .authorizeHttpRequests(reg -> reg
+                        .requestMatchers("/auth/login", "/public/**", "/error").permitAll()
+                        .anyRequest().authenticated()
+                )
+                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
+
+        return http.build();
+    }
+}
Index: src/main/java/mk/ukim/finki/synergymed/exceptions/EmailAlreadyExistsException.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/exceptions/EmailAlreadyExistsException.java	(revision 103984275890b6224c562adab88e9c76e8333980)
+++ src/main/java/mk/ukim/finki/synergymed/exceptions/EmailAlreadyExistsException.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -0,0 +1,4 @@
+package mk.ukim.finki.synergymed.exceptions;
+
+public class EmailAlreadyExistsException extends RuntimeException{
+}
Index: src/main/java/mk/ukim/finki/synergymed/exceptions/InvalidArgumentsException.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/exceptions/InvalidArgumentsException.java	(revision 103984275890b6224c562adab88e9c76e8333980)
+++ src/main/java/mk/ukim/finki/synergymed/exceptions/InvalidArgumentsException.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -0,0 +1,4 @@
+package mk.ukim.finki.synergymed.exceptions;
+
+public class InvalidArgumentsException extends RuntimeException{
+}
Index: src/main/java/mk/ukim/finki/synergymed/exceptions/InvalidEmailFormatException.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/exceptions/InvalidEmailFormatException.java	(revision 103984275890b6224c562adab88e9c76e8333980)
+++ src/main/java/mk/ukim/finki/synergymed/exceptions/InvalidEmailFormatException.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -0,0 +1,4 @@
+package mk.ukim.finki.synergymed.exceptions;
+
+public class InvalidEmailFormatException extends RuntimeException{
+}
Index: src/main/java/mk/ukim/finki/synergymed/exceptions/PasswordsMismatchException.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/exceptions/PasswordsMismatchException.java	(revision 103984275890b6224c562adab88e9c76e8333980)
+++ src/main/java/mk/ukim/finki/synergymed/exceptions/PasswordsMismatchException.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -0,0 +1,4 @@
+package mk.ukim.finki.synergymed.exceptions;
+
+public class PasswordsMismatchException extends RuntimeException{
+}
Index: src/main/java/mk/ukim/finki/synergymed/exceptions/UsernameAlreadyExistsException.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/exceptions/UsernameAlreadyExistsException.java	(revision 103984275890b6224c562adab88e9c76e8333980)
+++ src/main/java/mk/ukim/finki/synergymed/exceptions/UsernameAlreadyExistsException.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -0,0 +1,4 @@
+package mk.ukim.finki.synergymed.exceptions;
+
+public class UsernameAlreadyExistsException extends RuntimeException{
+}
Index: src/main/java/mk/ukim/finki/synergymed/models/Admin.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/models/Admin.java	(revision c87db71f06a0fe440ecc644244f411921061afd0)
+++ src/main/java/mk/ukim/finki/synergymed/models/Admin.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -11,5 +11,4 @@
 public class Admin {
     @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
     @Column(name = "user_id", nullable = false)
     private Integer id;
Index: src/main/java/mk/ukim/finki/synergymed/models/Client.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/models/Client.java	(revision c87db71f06a0fe440ecc644244f411921061afd0)
+++ src/main/java/mk/ukim/finki/synergymed/models/Client.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -11,5 +11,4 @@
 public class Client {
     @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
     @Column(name = "user_id", nullable = false)
     private Integer id;
Index: src/main/java/mk/ukim/finki/synergymed/models/Pharmacist.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/models/Pharmacist.java	(revision c87db71f06a0fe440ecc644244f411921061afd0)
+++ src/main/java/mk/ukim/finki/synergymed/models/Pharmacist.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -11,5 +11,4 @@
 public class Pharmacist {
     @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
     @Column(name = "user_id", nullable = false)
     private Integer id;
Index: src/main/java/mk/ukim/finki/synergymed/models/User.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/models/User.java	(revision c87db71f06a0fe440ecc644244f411921061afd0)
+++ src/main/java/mk/ukim/finki/synergymed/models/User.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -4,6 +4,10 @@
 import lombok.Getter;
 import lombok.Setter;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
 
 import java.time.LocalDate;
+import java.util.Collection;
+import java.util.List;
 
 @Getter
@@ -11,5 +15,5 @@
 @Entity
 @Table(name = "users", schema = "synergymed")
-public class User {
+public class User implements UserDetails{
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -41,3 +45,35 @@
     private LocalDate dateOfBirth;
 
+    private boolean isAccountNonExpired = true;
+    private boolean isAccountNonLocked = true;
+    private boolean isCredentialsNonExpired = true;
+    private boolean isEnabled = true;
+
+    @Override
+    public boolean isAccountNonExpired() {
+        return isAccountNonExpired;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        return isAccountNonLocked;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return isCredentialsNonExpired;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return isEnabled;
+    }
+
+    @Override public Collection<? extends GrantedAuthority> getAuthorities() { return List.of(); }
+
+    @Override public String getUsername() { return username; }
+    @Override
+    public String getPassword() {
+        return hashedPassword;
+    }
 }
Index: src/main/java/mk/ukim/finki/synergymed/repositories/UserRepository.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/repositories/UserRepository.java	(revision c87db71f06a0fe440ecc644244f411921061afd0)
+++ src/main/java/mk/ukim/finki/synergymed/repositories/UserRepository.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -4,4 +4,9 @@
 import org.springframework.data.jpa.repository.JpaRepository;
 
+import java.util.Optional;
+
 public interface UserRepository extends JpaRepository<User, Integer> {
+    Optional<User> findByUsername(String username);
+    Optional<User> findByEMail(String email);
+    boolean existsByUsername(String username);
 }
Index: src/main/java/mk/ukim/finki/synergymed/security/AppUserDetails.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/security/AppUserDetails.java	(revision 103984275890b6224c562adab88e9c76e8333980)
+++ src/main/java/mk/ukim/finki/synergymed/security/AppUserDetails.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -0,0 +1,38 @@
+package mk.ukim.finki.synergymed.security;
+
+import lombok.RequiredArgsConstructor;
+import mk.ukim.finki.synergymed.models.User;
+import mk.ukim.finki.synergymed.repositories.*;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.*;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+
+@Service
+@RequiredArgsConstructor
+public class AppUserDetails implements UserDetailsService {
+    private final UserRepository users;
+    private final AdminRepository admins;
+    private final PharmacistRepository pharmacists;
+    private final ClientRepository clients;
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        User u = users.findByUsername(username)
+                .orElseThrow(() -> new UsernameNotFoundException("Not found: " + username));
+        if (!u.isEnabled()) throw new RuntimeException("Verify/enable user first.");
+
+        var auths = new ArrayList<GrantedAuthority>();
+        if (admins.existsById(u.getId()))      auths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
+        if (pharmacists.existsById(u.getId())) auths.add(new SimpleGrantedAuthority("ROLE_PHARMACIST"));
+        if (clients.existsById(u.getId()))     auths.add(new SimpleGrantedAuthority("ROLE_CLIENT"));
+        if (auths.isEmpty())                   auths.add(new SimpleGrantedAuthority("ROLE_USER"));
+
+        return new org.springframework.security.core.userdetails.User(
+                u.getUsername(), u.getPassword(), u.isEnabled(),
+                u.isAccountNonExpired(), u.isCredentialsNonExpired(), u.isAccountNonLocked(), auths
+        );
+    }
+}
Index: src/main/java/mk/ukim/finki/synergymed/security/JwtAuthFilter.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/security/JwtAuthFilter.java	(revision 103984275890b6224c562adab88e9c76e8333980)
+++ src/main/java/mk/ukim/finki/synergymed/security/JwtAuthFilter.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -0,0 +1,54 @@
+package mk.ukim.finki.synergymed.security;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.lang.NonNull;
+import lombok.RequiredArgsConstructor;
+import mk.ukim.finki.synergymed.service.jwt.JwtService;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+@Component
+@RequiredArgsConstructor
+public class JwtAuthFilter extends OncePerRequestFilter {
+
+    private final JwtService jwtService;
+    private final UserDetailsService userDetailsService; // your AppUserDetails bean
+
+    @Override
+    protected void doFilterInternal(@NonNull HttpServletRequest req,
+                                    @NonNull HttpServletResponse res,
+                                    @NonNull FilterChain chain)
+            throws ServletException, IOException {
+
+        String header = req.getHeader("Authorization");
+        if (header != null && header.startsWith("Bearer ")) {
+            String token = header.substring(7);
+            try {
+                var claims = jwtService.parse(token).getPayload();
+                var username = claims.getSubject();
+
+                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+                    UserDetails ud = userDetailsService.loadUserByUsername(username);
+                    var auth = new UsernamePasswordAuthenticationToken(ud, null, ud.getAuthorities());
+                    auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
+                    SecurityContextHolder.getContext().setAuthentication(auth);
+                }
+
+                // TODO: 28.8.2025 HANDLE INVALID JWT TOKEN PROPERLY
+            } catch (Exception ignored) {
+                // invalid/expired token -> leave context unauthenticated
+            }
+        }
+        chain.doFilter(req, res);
+    }
+}
Index: src/main/java/mk/ukim/finki/synergymed/service/jwt/JwtService.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/service/jwt/JwtService.java	(revision 103984275890b6224c562adab88e9c76e8333980)
+++ src/main/java/mk/ukim/finki/synergymed/service/jwt/JwtService.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -0,0 +1,56 @@
+package mk.ukim.finki.synergymed.service.jwt;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+
+import javax.crypto.SecretKey;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.List;
+
+@Component
+public class JwtService {
+    private final SecretKey key;     // <— SecretKey
+    private final long expMillis;
+
+    public JwtService(
+            @Value("${security.jwt.secret:${SECURITY_JWT_SECRET:}}") String secret,
+            @Value("${security.jwt.exp-min:${SECURITY_JWT_EXP_MIN:60}}") long expMin
+    ) {
+        if (secret == null || secret.length() < 32) {
+            throw new IllegalStateException("security.jwt.secret (or env SECURITY_JWT_SECRET) must be ≥ 32 chars");
+        }
+        this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
+        this.expMillis = expMin * 60_000;
+    }
+
+    public String generate(UserDetails ud) {
+        Date now = new Date();
+        Date exp = new Date(now.getTime() + expMillis);
+        List<String> roles = ud.getAuthorities().stream()
+                .map(GrantedAuthority::getAuthority)
+                .toList();
+
+        return Jwts.builder()
+                .subject(ud.getUsername())     // 0.12.x
+                .claim("roles", roles)
+                .issuedAt(now)
+                .expiration(exp)
+                .signWith(key)                 // <— let JJWT infer HS256/384/512 from key size
+                .compact();
+    }
+
+    public Jws<Claims> parse(String token) {
+        return Jwts.parser()               // 0.12.x
+                .verifyWith(key)
+                .build()
+                .parseSignedClaims(token);
+    }
+}
Index: src/main/java/mk/ukim/finki/synergymed/web/LoginController.java
===================================================================
--- src/main/java/mk/ukim/finki/synergymed/web/LoginController.java	(revision 103984275890b6224c562adab88e9c76e8333980)
+++ src/main/java/mk/ukim/finki/synergymed/web/LoginController.java	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -0,0 +1,30 @@
+package mk.ukim.finki.synergymed.web;
+
+import lombok.RequiredArgsConstructor;
+import mk.ukim.finki.synergymed.service.jwt.JwtService;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/auth")
+@RequiredArgsConstructor
+public class LoginController {
+
+    private final AuthenticationManager authManager;
+    private final JwtService jwt;
+
+    public record LoginRequest(String username, String password) {}
+    public record LoginResponse(String token) {}
+
+    @PostMapping("/login")
+    public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest req) {
+        var auth = authManager.authenticate(
+                new UsernamePasswordAuthenticationToken(req.username(), req.password())
+        );
+        var ud = (UserDetails) auth.getPrincipal();
+        return ResponseEntity.ok(new LoginResponse(jwt.generate(ud)));
+    }
+}
Index: src/main/resources/application.properties
===================================================================
--- src/main/resources/application.properties	(revision c87db71f06a0fe440ecc644244f411921061afd0)
+++ src/main/resources/application.properties	(revision 103984275890b6224c562adab88e9c76e8333980)
@@ -1,1 +1,5 @@
 spring.application.name=SynergyMed
+
+# src/main/resources/application.properties
+security.jwt.secret=REPLACE_WITH_A_RANDOM_32+_CHAR_SECRET_KEY_1234567890abcdef
+security.jwt.exp-min=60
