This website requires JavaScript.
Authentication Flow (Spring Session JDBC, behind Caddy reverse proxy) Authentication Flow (Spring Session JDBC, behind Caddy reverse proxy) User Browser Caddy .TLS termination. Frontend .SvelteKit. Backend .Spring Boot. LoginRateLimiter DB User User Browser Browser Caddy (TLS termination) Caddy (TLS termination) Frontend (SvelteKit) Frontend (SvelteKit) Backend (Spring Boot) Backend (Spring Boot) LoginRateLimiter (Caffeine+Bucket4j) LoginRateLimiter (Caffeine+Bucket4j) DB DB Phase 2 of the auth rewrite (ADR-020, ADR-022 / #523, #524). Adds CSRF double-submit cookies, login rate limiting, and session revocation on password change/reset. Login (with rate limiting + CSRF bootstrap) Enter email + password HTTPS POST /?/login (form action) Caddy terminates TLS and forwards to Frontend over HTTP with: X-Forwarded-Proto: https X-Forwarded-For: <client IP> X-Forwarded-Host: archiv.raddatz.cloud HTTP POST /?/login + X-Forwarded-Proto: https POST /api/auth/login {email, password} + X-Forwarded-Proto: https server.forward-headers-strategy: native â request.getScheme() = "https" â Secure cookie flag set automatically. checkAndConsume(ip, email) [10/15min per ip+email; 20/15min per ip] alt [Rate limit exceeded] throw DomainException(TOO_MANY_LOGIN_ATTEMPTS) AuditService.log(LOGIN_RATE_LIMITED, {ip, email}) 429 Too Many Requests {"code":"TOO_MANY_LOGIN_ATTEMPTS"} Show rate-limit error [Under limit] AuthenticationManager authenticate(email, password) SELECT user WHERE email=? AppUser + groups + permissions BCrypt.matches(password, hash) (timing-safe: dummy hash on miss) getSession(true).setAttribute( SPRING_SECURITY_CONTEXT, ctx) INSERT spring_session + spring_session_attributes invalidateOnSuccess(ip, email) AuditService.log(LOGIN_SUCCESS, {userId, ip, ua}) 200 OK â AppUser Set-Cookie: fa_session=<opaque>; Path=/; HttpOnly; SameSite=Strict; Secure Set-Cookie: XSRF-TOKEN=<token>; Path=/; SameSite=Strict; Secure Parse Set-Cookie, re-emit fa_session (matches backend attrs) 303 â / Set-Cookie: fa_session=<opaque> HTTPS 303 + Set-Cookie Authenticated mutating request (CSRF double-submit) handleFetch in hooks.client.ts reads the XSRF-TOKEN cookie and injects X-XSRF-TOKEN header on every POST/PUT/PATCH/DELETE. HTTPS POST /api/... Cookie: fa_session=<opaque>; XSRF-TOKEN=<token> X-XSRF-TOKEN: <token> HTTP POST /api/... + Cookie + X-XSRF-TOKEN alt [X-XSRF-TOKEN missing or mismatched] 403 Forbidden {"code":"CSRF_TOKEN_MISSING"} HTTPS 403 [CSRF valid] SELECT * FROM spring_session WHERE SESSION_ID = ? session row Process request 2xx response + refreshed XSRF-TOKEN cookie HTTPS 2xx Authenticated read request HTTPS GET / Cookie: fa_session=<opaque> HTTP GET / + Cookie + X-Forwarded-Proto: https hooks.server.ts reads fa_session GET /api/users/me Cookie: fa_session=<opaque> SELECT * FROM spring_session WHERE SESSION_ID = ? row (or null if expired) alt [Session valid] UPDATE spring_session SET LAST_ACCESS_TIME = now 200 OK â AppUser rendered page HTTPS 200 [Session expired (idle > 8h) or unknown] 401 Unauthorized hooks: delete fa_session cookie 302 â /login?reason=expired HTTPS 302 Password change (revoke other sessions) POST /api/users/me/password {currentPassword, newPassword} + X-XSRF-TOKEN Verify currentPassword UPDATE app_users SET password_hash = ? DELETE spring_session WHERE principal = ? AND session_id != <current> revokeOtherSessions: caller stays logged in, all other devices are signed out. 204 No Content Password reset (revoke all sessions) POST /api/auth/reset-password {token, newPassword} Verify reset token UPDATE app_users SET password_hash = ? DELETE spring_session WHERE principal = ? revokeAllSessions: unauthenticated caller has no session to preserve â all sessions wiped. 204 No Content Logout HTTPS POST /logout HTTP POST /logout Cookie: fa_session=<opaque> POST /api/auth/logout Cookie: fa_session=<opaque> session.invalidate() SecurityContextHolder.clearContext() DELETE FROM spring_session WHERE SESSION_ID = ? AuditService.log(LOGOUT, {userId, ip, ua}) 204 No Content cookies.delete('fa_session') 303 â /login HTTPS 303 (cookie cleared)