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

51 lines
3.4 KiB
Markdown

# Authentication & Authorization Invariants — auth-migration
## 1. Authentication Rules
| ID | Rule | Trigger | Expected Behavior | Error Code |
|----|------|---------|-------------------|------------|
| AUTH-001 | JWT must contain `sub` claim | Any protected endpoint | Reject with 401 if `sub` missing/empty | `AUTH_MISSING_SUB` |
| AUTH-002 | JWT `exp` must be in future | Any protected endpoint | Reject with 401 if expired | `AUTH_TOKEN_EXPIRED` |
| AUTH-003 | JWT signature must verify against secret | Any protected endpoint | Reject with 401 if signature invalid | `AUTH_INVALID_SIGNATURE` |
| AUTH-004 | Bearer token required in Authorization header | Any protected endpoint | Reject with 401 if header missing | `AUTH_MISSING_TOKEN` |
| AUTH-005 | Token format must be `Bearer <token>` | Any protected endpoint | Reject with 401 if malformed | `AUTH_MALFORMED_TOKEN` |
## 2. Authorization Rules
| ID | Rule | Scope | Expected Behavior | Error Code |
|----|------|-------|-------------------|------------|
| AUTH-010 | ADMIN role required for user management | `/api/admin/**` | Reject with 403 if not ADMIN | `AUTH_FORBIDDEN_ADMIN` |
| AUTH-011 | Resource owner or ADMIN can modify | `/api/users/{id}/**` | Reject with 403 if neither | `AUTH_FORBIDDEN_RESOURCE` |
| AUTH-012 | Any authenticated user can read public profiles | `/api/users/public/**` | Allow 200 if authenticated | — |
| AUTH-013 | Role hierarchy: ADMIN > MANAGER > USER | All role-gated endpoints | Higher role inherits lower permissions | — |
## 3. Error Response Schema
| HTTP Status | Error Code | Response Body Shape |
|-------------|------------|---------------------|
| 401 | `AUTH_MISSING_TOKEN` | `{"error":"AUTH_MISSING_TOKEN","message":"Authorization header required","timestamp":<epoch>}` |
| 401 | `AUTH_MALFORMED_TOKEN` | `{"error":"AUTH_MALFORMED_TOKEN","message":"Bearer token format required","timestamp":<epoch>}` |
| 401 | `AUTH_TOKEN_EXPIRED` | `{"error":"AUTH_TOKEN_EXPIRED","message":"Token has expired","timestamp":<epoch>}` |
| 401 | `AUTH_INVALID_SIGNATURE` | `{"error":"AUTH_INVALID_SIGNATURE","message":"Token signature verification failed","timestamp":<epoch>}` |
| 401 | `AUTH_MISSING_SUB` | `{"error":"AUTH_MISSING_SUB","message":"Token missing subject claim","timestamp":<epoch>}` |
| 403 | `AUTH_FORBIDDEN_ADMIN` | `{"error":"AUTH_FORBIDDEN_ADMIN","message":"Admin role required","timestamp":<epoch>}` |
| 403 | `AUTH_FORBIDDEN_RESOURCE` | `{"error":"AUTH_FORBIDDEN_RESOURCE","message":"Not authorized for this resource","timestamp":<epoch>}` |
## 4. Session / Cookie Semantics
| ID | Rule | Behavior |
|----|------|----------|
| SESS-001 | Session cookie `HttpOnly` | Must be `true` |
| SESS-002 | Session cookie `Secure` | Must be `true` in production |
| SESS-003 | Session cookie `SameSite` | Must be `Lax` or `Strict` |
| SESS-004 | CSRF token required for state-changing ops | POST/PUT/DELETE without CSRF → 403 |
## 5. Migration Checklist
- [ ] Spring Security filter chain preserves order: JWT filter before session filter
- [ ] `@PreAuthorize` annotations match existing role-gate logic
- [ ] `AuthenticationEntryPoint` returns exact error schema (AUTH_* codes)
- [ ] `AccessDeniedHandler` returns exact error schema (AUTH_FORBIDDEN_* codes)
- [ ] Cookie attributes (`HttpOnly`, `Secure`, `SameSite`) set via `CookieSecurityConfigurer`
- [ ] CSRF protection enabled for state-changing endpoints
- [ ] Role hierarchy bean `RoleHierarchyImpl` wired with ADMIN > MANAGER > USER