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
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