codex-bais-final3-20260713-.../auth-migration/INVARIANTS.md

5.5 KiB

Authentication & Authorization Transition Invariants

1. Authentication Rules

1.1 Login Flow

Rule ID Description Invariant Error Code
AUTH-001 Username/password validation Credentials must match stored hash; empty credentials rejected AUTH_001
AUTH-002 Account lockout 5 failed attempts → 15min lockout AUTH_002
AUTH-003 Session creation Valid JWT issued with 30min TTL AUTH_003
AUTH-004 Password hashing BCrypt with cost factor 12 AUTH_004

1.2 Token Management

Rule ID Description Invariant Error Code
AUTH-010 JWT structure {sub, roles[], exp, iat} claims required AUTH_010
AUTH-011 Token refresh Refresh token valid for 7 days, one-time use AUTH_011
AUTH-012 Token revocation Logout invalidates token immediately AUTH_012

1.3 MFA Rules

Rule ID Description Invariant Error Code
AUTH-020 MFA required TOTP 6-digit code, 30s window, ±1 drift AUTH_020
AUTH-021 MFA bypass Only for TRUSTED_DEVICE users AUTH_021

2. Authorization Rules

2.1 Role Hierarchy

Rule ID Description Invariant Error Code
AUTH-030 Role precedence ADMIN > MANAGER > USER > GUEST AUTH_030
AUTH-031 Role assignment Only ADMIN can assign roles AUTH_031
AUTH-032 Self-demotion ADMIN cannot demote own account AUTH_032

2.2 Permission Boundaries

Rule ID Description Invariant Error Code
AUTH-040 Resource ownership Users can only modify own resources AUTH_040
AUTH-041 Cross-tenant access No cross-tenant data access AUTH_041
AUTH-042 Admin override ADMIN bypasses ownership check AUTH_042

2.3 API Endpoint Permissions

Rule ID Endpoint Pattern Required Role Invariant
AUTH-050 POST /auth/login GUEST Public endpoint
AUTH-051 POST /auth/logout USER+ Authenticated
AUTH-052 GET /admin/* ADMIN Admin only
AUTH-053 POST /users/* MANAGER+ Manager+ only
AUTH-054 GET /reports/* USER+ Authenticated

3. Error Response Format

3.1 Standard Error Structure

{
  "code": "AUTH_XXX",
  "message": "Human-readable message",
  "timestamp": "ISO-8601",
  "path": "/original/request/path"
}

3.2 HTTP Status Mapping

HTTP Status Condition
400 Invalid request format
401 Missing/invalid credentials
403 Insufficient permissions
404 Resource not found
423 Account locked
429 Rate limit exceeded
500 Internal server error

4. Security Invariants (Must Not Change)

Priority Invariant Rationale
CRITICAL Passwords never logged PII protection
CRITICAL Tokens never in URL query Prevent log leakage
CRITICAL Session fixation prevention CSRF protection
HIGH Rate limiting per IP DoS prevention
HIGH Audit logging on auth events Compliance

5. Spring Migration Checklist

  • SecurityConfig with BCryptPasswordEncoder bean
  • JwtAuthenticationFilter ordering (before UsernamePasswordAuthenticationFilter)
  • @EnableMethodSecurity for @PreAuthorize annotations
  • Custom AuthenticationEntryPoint for error responses
  • Custom AccessDeniedHandler for 403 responses
  • SessionManagement with fixed session fixation protection
  • CORS configuration aligned with existing origins

6. Implementation Reference

6.1 SecurityConfig Bean Configuration

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(12); // AUTH-004
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    return http
        .csrf(csrf -> csrf
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .ignoringRequestMatchers("/auth/login", "/auth/refresh")
        )
        .sessionManagement(s -> s
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .sessionFixation().migrateSession()
        )
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/auth/login", "/auth/refresh").permitAll()
            .requestMatchers("/admin/**").hasRole("ADMIN")
            .requestMatchers("/users/**").hasAnyRole("MANAGER", "ADMIN")
            .requestMatchers("/reports/**").authenticated()
            .anyRequest().authenticated()
        )
        .exceptionHandling(ex -> ex
            .authenticationEntryPoint(new CustomAuthEntryPoint())
            .accessDeniedHandler(new CustomAccessDeniedHandler())
        )
        .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
        .build();
}

6.2 JWT Claims Structure

public record JwtClaims(
    String subject,      // username
    List<String> roles,  // ["ADMIN", "USER"]
    Date issuedAt,
    Date expiration      // 30min for access, 7days for refresh
) {}

6.3 Lockout Logic

private static final int MAX_FAILED_ATTEMPTS = 5;
private static final long LOCKOUT_DURATION_MS = 15 * 60 * 1000;

6.4 Role Hierarchy Levels

private static final List<String> ROLE_HIERARCHY = 
    List.of("ADMIN", "MANAGER", "USER", "GUEST");
// Lower index = higher privilege