diff --git a/backend/pom.xml b/backend/pom.xml
index c03a991..ba34886 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -68,6 +68,26 @@
spring-security-test
test
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.12.3
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.12.3
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.12.3
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.3.0
+
diff --git a/backend/src/main/java/online/wesal/wesal/config/JwtAuthenticationFilter.java b/backend/src/main/java/online/wesal/wesal/config/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..2050e5c
--- /dev/null
+++ b/backend/src/main/java/online/wesal/wesal/config/JwtAuthenticationFilter.java
@@ -0,0 +1,58 @@
+package online.wesal.wesal.config;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import online.wesal.wesal.service.UserDetailsServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+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.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+@Component
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+ @Autowired
+ private JwtUtil jwtUtil;
+
+ @Autowired
+ private UserDetailsServiceImpl userDetailsService;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+
+ final String authorizationHeader = request.getHeader("Authorization");
+
+ String username = null;
+ String jwt = null;
+
+ if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
+ jwt = authorizationHeader.substring(7);
+ try {
+ username = jwtUtil.extractUsername(jwt);
+ } catch (Exception e) {
+ logger.error("JWT Token extraction failed", e);
+ }
+ }
+
+ if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+ UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
+
+ if (jwtUtil.validateToken(jwt, userDetails.getUsername())) {
+ UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
+ userDetails, null, userDetails.getAuthorities());
+ authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+ SecurityContextHolder.getContext().setAuthentication(authToken);
+ }
+ }
+
+ filterChain.doFilter(request, response);
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/online/wesal/wesal/config/JwtUtil.java b/backend/src/main/java/online/wesal/wesal/config/JwtUtil.java
new file mode 100644
index 0000000..2aa3f69
--- /dev/null
+++ b/backend/src/main/java/online/wesal/wesal/config/JwtUtil.java
@@ -0,0 +1,64 @@
+package online.wesal.wesal.config;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+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;
+import java.util.function.Function;
+
+@Component
+public class JwtUtil {
+
+ @Value("${jwt.secret:mySecretKey123456789012345678901234567890}")
+ private String secret;
+
+ @Value("${jwt.expiration:86400000}")
+ private Long expiration;
+
+ private SecretKey getSigningKey() {
+ return Keys.hmacShaKeyFor(secret.getBytes());
+ }
+
+ public String generateToken(String username) {
+ return Jwts.builder()
+ .subject(username)
+ .issuedAt(new Date())
+ .expiration(new Date(System.currentTimeMillis() + expiration))
+ .signWith(getSigningKey())
+ .compact();
+ }
+
+ public String extractUsername(String token) {
+ return extractClaim(token, Claims::getSubject);
+ }
+
+ public Date extractExpiration(String token) {
+ return extractClaim(token, Claims::getExpiration);
+ }
+
+ public T extractClaim(String token, Function claimsResolver) {
+ final Claims claims = extractAllClaims(token);
+ return claimsResolver.apply(claims);
+ }
+
+ private Claims extractAllClaims(String token) {
+ return Jwts.parser()
+ .verifyWith(getSigningKey())
+ .build()
+ .parseSignedClaims(token)
+ .getPayload();
+ }
+
+ public Boolean isTokenExpired(String token) {
+ return extractExpiration(token).before(new Date());
+ }
+
+ public Boolean validateToken(String token, String username) {
+ final String extractedUsername = extractUsername(token);
+ return (extractedUsername.equals(username) && !isTokenExpired(token));
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/online/wesal/wesal/config/OpenApiConfig.java b/backend/src/main/java/online/wesal/wesal/config/OpenApiConfig.java
new file mode 100644
index 0000000..5b85f6f
--- /dev/null
+++ b/backend/src/main/java/online/wesal/wesal/config/OpenApiConfig.java
@@ -0,0 +1,28 @@
+package online.wesal.wesal.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class OpenApiConfig {
+
+ @Bean
+ public OpenAPI customOpenAPI() {
+ return new OpenAPI()
+ .info(new Info()
+ .title("Wesal API")
+ .description("Social media application API")
+ .version("1.0.0"))
+ .addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"))
+ .components(new io.swagger.v3.oas.models.Components()
+ .addSecuritySchemes("Bearer Authentication",
+ new SecurityScheme()
+ .type(SecurityScheme.Type.HTTP)
+ .scheme("bearer")
+ .bearerFormat("JWT")));
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/online/wesal/wesal/config/SecurityConfig.java b/backend/src/main/java/online/wesal/wesal/config/SecurityConfig.java
new file mode 100644
index 0000000..e812afb
--- /dev/null
+++ b/backend/src/main/java/online/wesal/wesal/config/SecurityConfig.java
@@ -0,0 +1,78 @@
+package online.wesal.wesal.config;
+
+import online.wesal.wesal.service.UserDetailsServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+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;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+
+ @Autowired
+ private UserDetailsServiceImpl userDetailsService;
+
+ @Autowired
+ private JwtAuthenticationFilter jwtAuthenticationFilter;
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ public DaoAuthenticationProvider authenticationProvider() {
+ DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+ authProvider.setUserDetailsService(userDetailsService);
+ authProvider.setPasswordEncoder(passwordEncoder());
+ return authProvider;
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
+ return config.getAuthenticationManager();
+ }
+
+ @Bean
+ public CorsConfigurationSource corsConfigurationSource() {
+ CorsConfiguration configuration = new CorsConfiguration();
+ configuration.setAllowedOriginPatterns(Arrays.asList("*"));
+ configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
+ configuration.setAllowedHeaders(Arrays.asList("*"));
+ configuration.setAllowCredentials(true);
+
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", configuration);
+ return source;
+ }
+
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
+ .csrf(csrf -> csrf.disable())
+ .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/", "/login", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
+ .anyRequest().authenticated()
+ )
+ .authenticationProvider(authenticationProvider())
+ .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
+
+ return http.build();
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/online/wesal/wesal/controller/AuthController.java b/backend/src/main/java/online/wesal/wesal/controller/AuthController.java
new file mode 100644
index 0000000..e7934a8
--- /dev/null
+++ b/backend/src/main/java/online/wesal/wesal/controller/AuthController.java
@@ -0,0 +1,68 @@
+package online.wesal.wesal.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import online.wesal.wesal.dto.LoginRequest;
+import online.wesal.wesal.dto.LoginResponse;
+import online.wesal.wesal.dto.UsernameSelectionRequest;
+import online.wesal.wesal.entity.User;
+import online.wesal.wesal.service.AuthService;
+import online.wesal.wesal.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/")
+@CrossOrigin(origins = "*")
+@Tag(name = "Authentication", description = "Authentication endpoints")
+public class AuthController {
+
+ @Autowired
+ private AuthService authService;
+
+ @Autowired
+ private UserService userService;
+
+ @GetMapping("/")
+ @Operation(summary = "Health check", description = "Returns API status")
+ public ResponseEntity