Planned implementation: This guide describes the intended JWT authentication flow for OrgStack. The current codebase includes Spring Security as a dependency but does not yet implement authentication endpoints, JWT token generation, or user authentication logic.
OrgStack will use JSON Web Tokens (JWT) for stateless authentication. This guide explains how JWT authentication will be implemented with Spring Security and how tokens will carry both user identity and tenant context.
OrgStack’s authentication flow follows these steps:
1
User logs in with credentials
The client sends username and password to the /auth/login endpoint:
POST /api/auth/loginContent-Type: application/json{ "email": "[email protected]", "password": "securePassword123"}
2
Server validates credentials
Spring Security authenticates the credentials against the user database:
@Servicepublic class AuthService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final JwtTokenProvider jwtTokenProvider; public AuthResponse login(LoginRequest request) { User user = userRepository.findByEmail(request.getEmail()) .orElseThrow(() -> new BadCredentialsException("Invalid credentials")); if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { throw new BadCredentialsException("Invalid credentials"); } String token = jwtTokenProvider.createToken(user); return new AuthResponse(token, user); }}
3
Server generates JWT token
A JWT token is generated containing the user’s ID, email, and tenant ID:
public String createToken(User user) { Claims claims = Jwts.claims().setSubject(user.getId().toString()); claims.put("email", user.getEmail()); claims.put("tenantId", user.getTenantId().toString()); claims.put("roles", user.getRoles()); Date now = new Date(); Date validity = new Date(now.getTime() + validityInMilliseconds); return Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(validity) .signWith(secretKey, SignatureAlgorithm.HS256) .compact();}
The tenant ID is embedded in the JWT token, ensuring every request carries the user’s organizational context.
4
Client includes token in requests
The client stores the JWT token and includes it in the Authorization header for all subsequent requests:
GET /api/projectsAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
5
Server validates token
A JWT filter intercepts requests, validates the token, and sets the security context:
The security configuration defines which endpoints require authentication:
@Configuration@EnableWebSecurity@EnableMethodSecuritypublic class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/actuator/health").permitAll() .anyRequest().authenticated()) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }}
The configuration disables CSRF protection since JWT tokens are not vulnerable to CSRF attacks. Session management is set to STATELESS because JWTs provide authentication state.
@Servicepublic class UserService { private final PasswordEncoder passwordEncoder; private final UserRepository userRepository; public User registerUser(RegisterRequest request) { if (userRepository.existsByEmail(request.getEmail())) { throw new DuplicateEmailException("Email already registered"); } User user = new User(); user.setEmail(request.getEmail()); user.setPassword(passwordEncoder.encode(request.getPassword())); user.setTenantId(request.getTenantId()); return userRepository.save(user); }}
BCrypt automatically includes salt and uses multiple hashing rounds, making it resistant to rainbow table and brute-force attacks.