Index: pom.xml
===================================================================
--- pom.xml	(revision b1374a210fe729ba0646bdb59db4f21b3f7b6ec8)
+++ pom.xml	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -92,4 +92,25 @@
             <version>2.3.0</version>
         </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-api</artifactId>
+            <version>0.11.2</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-impl</artifactId>
+            <version>0.11.2</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-jackson</artifactId>
+            <version>0.11.2</version>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
 
Index: src/main/java/finki/db/tasty_tabs/config/OpenApiConfig.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/config/OpenApiConfig.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/config/OpenApiConfig.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,73 @@
+package finki.db.tasty_tabs.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import io.swagger.v3.core.jackson.ModelResolver;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.media.StringSchema;
+import io.swagger.v3.oas.models.parameters.Parameter;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.servers.Server;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.springdoc.core.customizers.OpenApiCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+@Configuration
+public class OpenApiConfig {
+    private static final List<ParameterItem> GLOBAL_PARAMETERS = List.of(
+            new ParameterItem("Accept-Language", "Response Language", false, "header", "string")
+    );
+
+    @Bean
+    public ModelResolver modelResolver(ObjectMapper objectMapper) {
+        return new ModelResolver(objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE));
+    }
+
+    @Bean
+    public OpenApiCustomizer globalHeaderCustomizer() {
+        return openApi -> openApi.getPaths().values().forEach(pathItem ->
+                pathItem.readOperations().forEach(operation -> {
+                    GLOBAL_PARAMETERS.forEach(parameterItem -> {
+                        Parameter parameter = new Parameter()
+                                .in(parameterItem.getIn())
+                                .name(parameterItem.getName())
+                                .description(parameterItem.getDescription())
+                                .required(parameterItem.isRequired())
+                                .schema(new StringSchema());
+                        operation.addParametersItem(parameter);
+                    });
+                }));
+    }
+
+    @Bean
+    public OpenAPI customOpenAPI() {
+        return new OpenAPI()
+                .components(new Components()
+                        .addSecuritySchemes("bearerAuth",
+                                new SecurityScheme()
+                                        .type(SecurityScheme.Type.HTTP)
+                                        .scheme("bearer")
+                                        .bearerFormat("JWT")
+                        )
+                )
+                .addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
+                .addServersItem(new Server().url("http://localhost:8080").description("Local server"));
+    }
+
+    @Data
+    @AllArgsConstructor
+    private static class ParameterItem {
+        private String name;
+        private String description;
+        private boolean required;
+        private String in;
+        private String type;
+
+    }
+}
Index: src/main/java/finki/db/tasty_tabs/config/WebConfig.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/config/WebConfig.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/config/WebConfig.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,22 @@
+package finki.db.tasty_tabs.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+    public WebConfig() {
+    }
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedOrigins("*")
+                .allowedMethods("*")
+                .allowedHeaders("*")
+                .allowCredentials(false)
+                .maxAge(3600);
+    }
+}
Index: src/main/java/finki/db/tasty_tabs/entity/exceptions/CategoryNotFoundException.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/entity/exceptions/CategoryNotFoundException.java	(revision b1374a210fe729ba0646bdb59db4f21b3f7b6ec8)
+++ src/main/java/finki/db/tasty_tabs/entity/exceptions/CategoryNotFoundException.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -1,5 +1,5 @@
 package finki.db.tasty_tabs.entity.exceptions;
 
-public class CategoryNotFoundException extends RuntimeException {
+public class CategoryNotFoundException extends DomainException {
 
     public CategoryNotFoundException(String name) {
@@ -7,5 +7,5 @@
     }
     public CategoryNotFoundException() {
-        super(String.format("Category doesnt exist"));
+        super("Category doesnt exist");
     }
 }
Index: src/main/java/finki/db/tasty_tabs/entity/exceptions/DomainException.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/entity/exceptions/DomainException.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/entity/exceptions/DomainException.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,7 @@
+package finki.db.tasty_tabs.entity.exceptions;
+
+public abstract class DomainException extends RuntimeException {
+    public DomainException(String message) {
+        super(message);
+    }
+}
Index: src/main/java/finki/db/tasty_tabs/entity/exceptions/ProductNotFoundException.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/entity/exceptions/ProductNotFoundException.java	(revision b1374a210fe729ba0646bdb59db4f21b3f7b6ec8)
+++ src/main/java/finki/db/tasty_tabs/entity/exceptions/ProductNotFoundException.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -1,5 +1,5 @@
 package finki.db.tasty_tabs.entity.exceptions;
 
-public class ProductNotFoundException extends RuntimeException {
+public class ProductNotFoundException extends DomainException {
 
     public ProductNotFoundException(Long id) {
Index: src/main/java/finki/db/tasty_tabs/entity/exceptions/ProductNotInStockException.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/entity/exceptions/ProductNotInStockException.java	(revision b1374a210fe729ba0646bdb59db4f21b3f7b6ec8)
+++ src/main/java/finki/db/tasty_tabs/entity/exceptions/ProductNotInStockException.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -1,5 +1,5 @@
 package finki.db.tasty_tabs.entity.exceptions;
 
-public class ProductNotInStockException extends RuntimeException {
+public class ProductNotInStockException extends DomainException {
 
     public ProductNotInStockException(String name) {
Index: src/main/java/finki/db/tasty_tabs/entity/exceptions/TableNumberAlreadyExistsException.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/entity/exceptions/TableNumberAlreadyExistsException.java	(revision b1374a210fe729ba0646bdb59db4f21b3f7b6ec8)
+++ src/main/java/finki/db/tasty_tabs/entity/exceptions/TableNumberAlreadyExistsException.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -1,5 +1,5 @@
 package finki.db.tasty_tabs.entity.exceptions;
 
-public class TableNumberAlreadyExistsException extends RuntimeException {
+public class TableNumberAlreadyExistsException extends DomainException {
 
     public TableNumberAlreadyExistsException(Integer number) {
Index: src/main/java/finki/db/tasty_tabs/repository/UserRepository.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/repository/UserRepository.java	(revision b1374a210fe729ba0646bdb59db4f21b3f7b6ec8)
+++ src/main/java/finki/db/tasty_tabs/repository/UserRepository.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -4,3 +4,7 @@
 import org.springframework.data.jpa.repository.JpaRepository;
 
-public interface UserRepository extends JpaRepository<User, Long> {}
+import java.util.Optional;
+
+public interface UserRepository extends JpaRepository<User, Long> {
+    Optional<User> findByEmail(String email);
+}
Index: src/main/java/finki/db/tasty_tabs/service/AuthService.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/service/AuthService.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/service/AuthService.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,11 @@
+package finki.db.tasty_tabs.service;
+
+import finki.db.tasty_tabs.web.dto.AuthDto;
+import finki.db.tasty_tabs.web.dto.RegisterRequest;
+import finki.db.tasty_tabs.web.dto.UserDto;
+
+public interface AuthService {
+    AuthDto authenticate(String username, String password);
+    AuthDto register(RegisterRequest request);
+    UserDto getAuthenticatedUser();
+}
Index: src/main/java/finki/db/tasty_tabs/service/impl/AuthServiceImpl.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/service/impl/AuthServiceImpl.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/service/impl/AuthServiceImpl.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,66 @@
+package finki.db.tasty_tabs.service.impl;
+
+import finki.db.tasty_tabs.entity.User;
+import finki.db.tasty_tabs.repository.UserRepository;
+import finki.db.tasty_tabs.service.AuthService;
+import finki.db.tasty_tabs.utils.JwtProvider;
+import finki.db.tasty_tabs.web.dto.AuthDto;
+import finki.db.tasty_tabs.web.dto.RegisterRequest;
+import finki.db.tasty_tabs.web.dto.UserDto;
+import jakarta.transaction.Transactional;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.util.HashSet;
+
+@Service
+public class AuthServiceImpl implements AuthService {
+    private final UserRepository userRepository;
+    private final JwtProvider jwtProvider;
+    private final AuthenticationManager authenticationManager;
+    private final PasswordEncoder passwordEncoder;
+
+    public AuthServiceImpl(UserRepository userRepository, JwtProvider jwtProvider, AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder) {
+        this.userRepository = userRepository;
+        this.jwtProvider = jwtProvider;
+        this.authenticationManager = authenticationManager;
+        this.passwordEncoder = passwordEncoder;
+    }
+
+    @Override
+    public AuthDto authenticate(String email, String password) {
+        Authentication authentication = authenticationManager.authenticate(
+                new UsernamePasswordAuthenticationToken(email, password)
+        );
+
+        User user = userRepository.findByEmail(email)
+                .orElseThrow(() -> new RuntimeException("User not found"));
+
+        String token = jwtProvider.generateToken(user.getEmail());
+
+        return new AuthDto(
+                token,
+                UserDto.from(user)
+        );
+    }
+
+
+    @Override
+    @Transactional
+    public AuthDto register(RegisterRequest request) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    @Override
+    public UserDto getAuthenticatedUser() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+        return UserDto.from(userRepository.findByEmail(authentication.getName())
+                .orElseThrow(() -> new RuntimeException("User not found")));
+    }
+
+}
Index: src/main/java/finki/db/tasty_tabs/utils/JwtProvider.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/utils/JwtProvider.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/utils/JwtProvider.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,36 @@
+package finki.db.tasty_tabs.utils;
+
+import io.jsonwebtoken.Claims;
+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 java.util.Date;
+
+@Component
+public class JwtProvider {
+    @Value("${jwt.secret}")
+    private String jwtSecret;
+    @Value("${jwt.expiration}")
+    private long jwtExpiration;
+
+    public String generateToken(String email) {
+        return Jwts.builder()
+                .setSubject(String.valueOf(email)) // Use user ID as the subject
+                .setIssuedAt(new Date()) // Token issue time
+                .setExpiration(new Date(System.currentTimeMillis() + jwtExpiration)) // Token expiration
+                .signWith(Keys.hmacShaKeyFor(jwtSecret.getBytes()), SignatureAlgorithm.HS256) // HMAC-SHA256 signing
+                .compact();
+    }
+
+    public Claims validateToken(String token) {
+        return Jwts.parserBuilder()
+                .setSigningKey(jwtSecret.getBytes())
+                .build()
+                .parseClaimsJws(token)
+                .getBody();
+    }
+}
+
Index: src/main/java/finki/db/tasty_tabs/web/controllers/AuthController.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/web/controllers/AuthController.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/web/controllers/AuthController.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,4 @@
+package finki.db.tasty_tabs.web.controllers;
+
+public class AuthController {
+}
Index: src/main/java/finki/db/tasty_tabs/web/dto/AuthDto.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/web/dto/AuthDto.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/web/dto/AuthDto.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,7 @@
+package finki.db.tasty_tabs.web.dto;
+
+public record AuthDto (
+        String token,
+        UserDto user
+){
+}
Index: src/main/java/finki/db/tasty_tabs/web/dto/RegisterRequest.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/web/dto/RegisterRequest.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/web/dto/RegisterRequest.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,17 @@
+package finki.db.tasty_tabs.web.dto;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Data
+public class RegisterRequest {
+    @NotNull(message = "Email is required")
+    private String email;
+    @NotNull(message = "Password is required")
+    private String password;
+    private String passwordConfirmation;
+    private String firstName;
+    private String lastName;
+
+
+}
Index: src/main/java/finki/db/tasty_tabs/web/dto/UserDto.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/web/dto/UserDto.java	(revision b1374a210fe729ba0646bdb59db4f21b3f7b6ec8)
+++ src/main/java/finki/db/tasty_tabs/web/dto/UserDto.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -1,8 +1,12 @@
 package finki.db.tasty_tabs.web.dto;
+import finki.db.tasty_tabs.entity.User;
+import lombok.AllArgsConstructor;
 import lombok.Data;
 import java.time.LocalDateTime;
 import java.util.List;
+
 @Data
-class UserDto {
+@AllArgsConstructor
+public class UserDto {
     private Long id;
     private String email;
@@ -10,3 +14,13 @@
     private String city;
     private String phoneNumber;
+
+    public static UserDto from(User user) {
+        return new UserDto(
+                user.getId(),
+                user.getEmail(),
+                user.getStreet(),
+                user.getCity(),
+                user.getPhoneNumber()
+        );
+    }
 }
Index: src/main/java/finki/db/tasty_tabs/web/exception/FilterExceptionHandler.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/web/exception/FilterExceptionHandler.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/web/exception/FilterExceptionHandler.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,75 @@
+package finki.db.tasty_tabs.web.exception;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.jsonwebtoken.ExpiredJwtException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.ProblemDetail;
+import org.springframework.security.authentication.AccountStatusException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authorization.AuthorizationDeniedException;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.AccessDeniedException;
+import java.security.SignatureException;
+
+@Slf4j
+public class FilterExceptionHandler {
+
+    // Handle an exception within a filter using the same ErrorResponse structure.
+    public static void handleException(HttpServletRequest request,
+                                       HttpServletResponse response,
+                                       Exception exception) throws IOException {
+        // Convert exception -> ErrorResponse just like your GlobalExceptionHandler.
+        ProblemDetail errorDetail = createErrorResponse(exception);
+        errorDetail.setInstance(URI.create(request.getRequestURI()));
+
+        // Write the ErrorResponse as JSON to response
+        response.setStatus(errorDetail.getStatus());
+        response.setContentType("application/json");
+
+        // For JSON serialization, you can use Jackson's ObjectMapper if available.
+        String json = new ObjectMapper().writeValueAsString(errorDetail);
+        response.getWriter().write(json);
+        response.getWriter().flush();
+    }
+
+    private static ProblemDetail createErrorResponse(Exception exception) {
+        ProblemDetail detail = null;
+
+        if (exception instanceof BadCredentialsException) {
+            detail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(401), exception.getMessage());
+            detail.setProperty("description", "The username or password is incorrect");
+        } else if (exception instanceof AccountStatusException) {
+            detail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
+            detail.setProperty("description", "The account is locked");
+        } else if (exception instanceof AccessDeniedException) {
+            detail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
+            detail.setProperty("description", "You are not authorized to access this resource");
+        } else if (exception instanceof SignatureException) {
+            detail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
+            detail.setProperty("description", "The JWT signature is invalid");
+        } else if (exception instanceof ExpiredJwtException) {
+            detail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(401), exception.getMessage());
+            detail.setProperty("description", "The JWT token has expired");
+        } else if (exception instanceof AuthorizationDeniedException) {
+            detail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
+            detail.setProperty("description", "You are not authorized to access this resource");
+        }
+
+        if (detail == null) {
+            // Unknown/unhandled exception
+            detail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(500), exception.getMessage());
+            detail.setProperty("description", "Unknown internal server error.");
+        }
+
+        log.warn("An exception occurred: {}", exception.getMessage(), exception);
+
+        return detail;
+    }
+}
+
+
Index: src/main/java/finki/db/tasty_tabs/web/exception/GlobalExceptionHandler.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/web/exception/GlobalExceptionHandler.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/web/exception/GlobalExceptionHandler.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,92 @@
+package finki.db.tasty_tabs.web.exception;
+
+import finki.db.tasty_tabs.entity.exceptions.DomainException;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.security.SignatureException;
+import io.swagger.v3.oas.annotations.Hidden;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.ProblemDetail;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.AccountStatusException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authorization.AuthorizationDeniedException;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@RestControllerAdvice
+@Hidden
+@Slf4j
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler({JwtException.class, AuthenticationException.class, SignatureException.class, AccessDeniedException.class})
+    public ProblemDetail handleSecurityExceptions(Exception exception) {
+        ProblemDetail errorDetail = null;
+
+        log.warn("Security exception: {}", exception.getMessage());
+        if (exception instanceof BadCredentialsException) {
+            errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(401), exception.getMessage());
+            errorDetail.setProperty("description", "The username or password is incorrect");
+
+        }
+
+        if (exception instanceof AccountStatusException) {
+            errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
+            errorDetail.setProperty("description", "The account is locked");
+        }
+
+        if (exception instanceof AuthorizationDeniedException){
+            errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
+            errorDetail.setProperty("description", "You are not authorized to access this resource");
+        }
+
+        if (exception instanceof AccessDeniedException) {
+            errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
+            errorDetail.setProperty("description", "You are not authorized to access this resource");
+        }
+
+        if (exception instanceof SignatureException) {
+            errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
+            errorDetail.setProperty("description", "The JWT signature is invalid");
+        }
+
+        if (exception instanceof ExpiredJwtException) {
+            errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(403), exception.getMessage());
+            errorDetail.setProperty("description", "The JWT token has expired");
+        }
+
+        if (errorDetail == null) {
+            errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(500), exception.getMessage());
+            errorDetail.setProperty("description", "Unknown internal server error.");
+        }
+
+        return errorDetail;
+    }
+
+    @ExceptionHandler(DomainException.class)
+    public ProblemDetail handleDomainExceptions(DomainException exception) {
+        ProblemDetail errorDetail = null;
+
+        log.warn("Domain exception: {}", exception.getMessage());
+
+        if (errorDetail == null) {
+            errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(500), exception.getMessage());
+            errorDetail.setProperty("description", "Unknown internal server error.");
+        }
+
+        return errorDetail;
+    }
+
+
+    @ExceptionHandler(Exception.class)
+    public ProblemDetail handleAllExceptions(Exception exception) {
+        ProblemDetail errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.valueOf(500), exception.getMessage());
+        errorDetail.setProperty("description", "Unknown internal server error.");
+
+        log.error("Unexpected exception: {}", exception.getMessage(), exception);
+
+        return errorDetail;
+    }
+}
Index: src/main/java/finki/db/tasty_tabs/web/filter/HttpLoggingFilter.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/web/filter/HttpLoggingFilter.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/web/filter/HttpLoggingFilter.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,59 @@
+package finki.db.tasty_tabs.web.filter;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+@Component
+@Slf4j
+public class HttpLoggingFilter extends OncePerRequestFilter {
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest req,
+                                    HttpServletResponse resp,
+                                    FilterChain chain)
+            throws ServletException, IOException {
+
+        long start = System.currentTimeMillis();
+        try {
+            chain.doFilter(req, resp);
+        } finally {
+            MDC.put("userId", resolveUserId());
+
+            // — compute duration —
+            long timeMs = System.currentTimeMillis() - start;
+            MDC.put("timeMs", String.valueOf(timeMs));
+
+            // — log it —
+            log.info("Request: {} {} {} from {} – {}ms",
+                    resp.getStatus(),
+                    req.getMethod(),
+                    req.getRequestURI(),
+                    req.getRemoteAddr(),
+                    timeMs);
+
+            // — cleanup —
+            MDC.clear();
+        }
+    }
+
+    private String resolveUserId() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication == null || authentication instanceof AnonymousAuthenticationToken) {
+            return "ANONYMOUS";
+        }
+        return ((UserDetails) authentication.getPrincipal()).getUsername();
+    }
+}
+
Index: src/main/java/finki/db/tasty_tabs/web/filter/JwtAuthenticationFilter.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/web/filter/JwtAuthenticationFilter.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/web/filter/JwtAuthenticationFilter.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,89 @@
+package finki.db.tasty_tabs.web.filter;
+
+import finki.db.tasty_tabs.utils.JwtProvider;
+import finki.db.tasty_tabs.web.exception.FilterExceptionHandler;
+import finki.db.tasty_tabs.web.security.CustomUserDetailsService;
+import finki.db.tasty_tabs.web.security.PublicUrlProvider;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.JwtException;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.authorization.AuthorizationDeniedException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+@Component
+@Slf4j
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+    private final JwtProvider jwtProvider;
+    private final CustomUserDetailsService userDetailsService;
+    private final PublicUrlProvider publicUrlProvider;
+    private final AntPathMatcher pathMatcher = new AntPathMatcher();
+
+    // Update the constructor
+    public JwtAuthenticationFilter(JwtProvider jwtProvider, CustomUserDetailsService userDetailsService, PublicUrlProvider publicUrlProvider) {
+        this.jwtProvider = jwtProvider;
+        this.userDetailsService = userDetailsService;
+        this.publicUrlProvider = publicUrlProvider;
+    }
+
+    // --- NEW METHOD ---
+    @Override
+    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
+        // This method tells Spring to SKIP this filter entirely if the path matches.
+        return publicUrlProvider.getPublicPaths().stream()
+                .anyMatch(p -> pathMatcher.match(p, request.getServletPath()));
+    }
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request,
+                                    HttpServletResponse response,
+                                    FilterChain filterChain) throws ServletException, IOException {
+        try {
+            String token = resolveToken(request);
+            Claims claims = jwtProvider.validateToken(token);
+            if (claims != null) {
+                String userId = claims.getSubject();
+                UserDetails userDetails = userDetailsService.loadUserByUserId(Long.parseLong(userId));
+
+                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
+                        userDetails, null, userDetails.getAuthorities());
+                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+
+                SecurityContextHolder.getContext().setAuthentication(authentication);
+                log.debug("JWT token validated");
+            }
+            filterChain.doFilter(request, response);
+        } catch (ExpiredJwtException e) {
+            log.debug("JWT token expired: {}", e.getMessage());
+            FilterExceptionHandler.handleException(request, response, e);
+        } catch (AuthorizationDeniedException e) {
+            log.debug("Authorization denied: {}", e.getMessage());
+            FilterExceptionHandler.handleException(request, response, e);
+        } catch (JwtException e) {
+            log.debug("JWT token invalid: {}", e.getMessage());
+            filterChain.doFilter(request, response);
+        } catch (Exception e) {
+            FilterExceptionHandler.handleException(request, response, e);
+        }
+    }
+
+    private String resolveToken(HttpServletRequest request) {
+        String bearerToken = request.getHeader("Authorization");
+        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
+            return bearerToken.substring(7);
+        }
+        return null;
+    }
+}
Index: src/main/java/finki/db/tasty_tabs/web/security/CustomUserDetailsService.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/web/security/CustomUserDetailsService.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/web/security/CustomUserDetailsService.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,8 @@
+package finki.db.tasty_tabs.web.security;
+
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+
+public interface CustomUserDetailsService extends UserDetailsService {
+    UserDetails loadUserByUserId(Long userId);
+}
Index: src/main/java/finki/db/tasty_tabs/web/security/PublicUrlProvider.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/web/security/PublicUrlProvider.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/web/security/PublicUrlProvider.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,18 @@
+package finki.db.tasty_tabs.web.security;
+
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class PublicUrlProvider {
+    // This list should exactly match the permitAll() paths in your SecurityConfig
+    private static final List<String> PUBLIC_PATHS = List.of(
+            "/swagger-ui/**",
+            "/v3/api-docs/**"
+    );
+
+    public List<String> getPublicPaths() {
+        return PUBLIC_PATHS;
+    }
+}
Index: src/main/java/finki/db/tasty_tabs/web/security/SecurityConfig.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/web/security/SecurityConfig.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/web/security/SecurityConfig.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,71 @@
+package finki.db.tasty_tabs.web.security;
+
+import finki.db.tasty_tabs.web.filter.JwtAuthenticationFilter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+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.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Configuration
+@EnableWebSecurity
+@EnableMethodSecurity
+public class SecurityConfig {
+    @Autowired
+    private JwtAuthenticationFilter jwtAuthenticationFilter;
+
+    @Bean
+    public SecurityFilterChain securityFilterChain(HttpSecurity http, PublicUrlProvider publicUrlProvider) throws Exception {
+        http
+                .csrf(AbstractHttpConfigurer::disable)
+                .cors(cors -> cors.configurationSource(corsConfigurationSource())) // Enable CORS
+                .authorizeHttpRequests(authorize -> authorize
+                        .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
+                        .requestMatchers(publicUrlProvider.getPublicPaths().toArray(new String[0])).permitAll()
+                        .anyRequest().authenticated()
+                )
+                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
+
+        return http.build();
+    }
+
+    @Bean
+    public CorsConfigurationSource corsConfigurationSource() {
+        CorsConfiguration config = new CorsConfiguration();
+        config.setAllowedOrigins(List.of("*")); // Or specific origins
+        config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
+        config.setAllowedHeaders(List.of("*"));
+        config.setAllowCredentials(false);
+        config.setMaxAge(3600L);
+
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        source.registerCorsConfiguration("/**", config);
+        return source;
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    @Bean
+    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
+        return authenticationConfiguration.getAuthenticationManager();
+    }
+
+}
Index: src/main/java/finki/db/tasty_tabs/web/security/UserDetailsServiceImpl.java
===================================================================
--- src/main/java/finki/db/tasty_tabs/web/security/UserDetailsServiceImpl.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
+++ src/main/java/finki/db/tasty_tabs/web/security/UserDetailsServiceImpl.java	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -0,0 +1,76 @@
+package finki.db.tasty_tabs.web.security;
+
+import finki.db.tasty_tabs.entity.*;
+import finki.db.tasty_tabs.repository.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Slf4j
+@Service
+public class UserDetailsServiceImpl implements CustomUserDetailsService {
+    @Autowired
+    private UserRepository userRepository;
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        log.debug("Loading user by username: {}", username);
+        User user = userRepository.findByEmail(username)
+                .orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + username));
+
+        return getUserDetails(user);
+    }
+
+    @Override
+    public UserDetails loadUserByUserId(Long userId) {
+        log.debug("Loading user by ID: {}", userId);
+        User user = userRepository.findById(userId)
+                .orElseThrow(() -> new UsernameNotFoundException("User not found with ID: " + userId));
+
+        return getUserDetails(user);
+    }
+
+    private UserDetails getUserDetails(User user){
+
+
+        // Convert roles to Spring Security's authorities
+        Set<GrantedAuthority> authorities = new HashSet<>();
+        GrantedAuthority grantedAuthority = getGrantedAuthority(user);
+
+        authorities.add(grantedAuthority);
+        log.debug("Applying role {} to user {}", grantedAuthority.getAuthority(), user.getId());
+
+        return new org.springframework.security.core.userdetails.User(
+                user.getEmail(),
+                user.getPassword(),
+                authorities
+        );
+    }
+
+    private static GrantedAuthority getGrantedAuthority(User user) {
+        GrantedAuthority grantedAuthority;
+
+        if(user instanceof Employee){
+            if(user instanceof Manager){
+                grantedAuthority = new SimpleGrantedAuthority("ROLE_MANAGER");
+            } else if(user instanceof FrontStaff){
+                grantedAuthority = new SimpleGrantedAuthority("ROLE_FRONT_STAFF");
+            } else if (user instanceof BackStaff){
+                grantedAuthority = new SimpleGrantedAuthority("ROLE_BACKSTAFF");
+            } else {
+                grantedAuthority = new SimpleGrantedAuthority("ROLE_EMPLOYEE");
+            }
+        } else{
+            grantedAuthority = new SimpleGrantedAuthority("ROLE_CUSTOMER");
+        }
+        return grantedAuthority;
+    }
+}
+
Index: src/main/resources/application.properties
===================================================================
--- src/main/resources/application.properties	(revision b1374a210fe729ba0646bdb59db4f21b3f7b6ec8)
+++ src/main/resources/application.properties	(revision 95ca050f225f9df7bb5a7f185c1e88a703282ff4)
@@ -3,3 +3,2 @@
 spring.datasource.username=your_db_user
 spring.datasource.password=your_db_password
-
