docs: rewrite seq-auth-flow.puml for the Spring Session model (ADR-020)
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m4s
CI / OCR Service Tests (pull_request) Successful in 20s
CI / Backend Unit Tests (pull_request) Successful in 3m6s
CI / fail2ban Regex (pull_request) Successful in 40s
CI / Semgrep Security Scan (pull_request) Successful in 17s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m0s
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m4s
CI / OCR Service Tests (pull_request) Successful in 20s
CI / Backend Unit Tests (pull_request) Successful in 3m6s
CI / fail2ban Regex (pull_request) Successful in 40s
CI / Semgrep Security Scan (pull_request) Successful in 17s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m0s
Removes the cookie-promotion step (auth_token → Authorization: Basic) and splits the diagram into three labelled phases: Login, Authenticated request, Logout. Adds the spring_session DB round-trip on every authenticated request and the alt branch for an expired session returning 401 → /login?reason=expired. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,21 @@
|
|||||||
@startuml
|
@startuml
|
||||||
title Authentication Flow (behind Caddy reverse proxy)
|
title Authentication Flow (Spring Session JDBC, behind Caddy reverse proxy)
|
||||||
|
note over Browser, DB
|
||||||
|
Phase 1 of the auth rewrite (ADR-020 / #523).
|
||||||
|
Replaces the Basic-credentials-in-cookie model
|
||||||
|
with an opaque server-side session id (fa_session).
|
||||||
|
end note
|
||||||
|
|
||||||
actor User
|
actor User
|
||||||
participant Browser
|
participant Browser
|
||||||
participant "Caddy (TLS termination)" as Caddy
|
participant "Caddy (TLS termination)" as Caddy
|
||||||
participant "Frontend (SvelteKit)" as Frontend
|
participant "Frontend (SvelteKit)" as Frontend
|
||||||
participant "Backend (Spring Boot)" as Backend
|
participant "Backend (Spring Boot)" as Backend
|
||||||
participant PostgreSQL as DB
|
participant "spring_session\n(PostgreSQL)" as DB
|
||||||
|
|
||||||
|
== Login ==
|
||||||
User -> Browser: Enter email + password
|
User -> Browser: Enter email + password
|
||||||
Browser -> Caddy: HTTPS POST /login (form action)
|
Browser -> Caddy: HTTPS POST /?/login (form action)
|
||||||
note right of Caddy
|
note right of Caddy
|
||||||
Caddy terminates TLS and forwards
|
Caddy terminates TLS and forwards
|
||||||
to Frontend over HTTP with:
|
to Frontend over HTTP with:
|
||||||
@@ -17,33 +23,54 @@ note right of Caddy
|
|||||||
X-Forwarded-For: <client IP>
|
X-Forwarded-For: <client IP>
|
||||||
X-Forwarded-Host: archiv.raddatz.cloud
|
X-Forwarded-Host: archiv.raddatz.cloud
|
||||||
end note
|
end note
|
||||||
Caddy -> Frontend: HTTP POST /login\n+ X-Forwarded-Proto: https
|
Caddy -> Frontend: HTTP POST /?/login + X-Forwarded-Proto: https
|
||||||
Frontend -> Frontend: Base64 encode "email:password"
|
Frontend -> Backend: POST /api/auth/login\n{email, password}\n+ X-Forwarded-Proto: https
|
||||||
Frontend -> Backend: GET /api/users/me\nAuthorization: Basic <token>\n+ X-Forwarded-Proto: https
|
|
||||||
note right of Backend
|
note right of Backend
|
||||||
server.forward-headers-strategy: native
|
server.forward-headers-strategy: native
|
||||||
Jetty's ForwardedRequestCustomizer
|
→ request.getScheme() = "https"
|
||||||
reads X-Forwarded-Proto so
|
→ Secure cookie flag set automatically.
|
||||||
request.getScheme() returns "https".
|
|
||||||
end note
|
end note
|
||||||
Backend -> Backend: Spring Security parses Basic Auth
|
Backend -> Backend: AuthenticationManager\nauthenticate(email, password)
|
||||||
Backend -> DB: SELECT user WHERE email=?
|
Backend -> DB: SELECT user WHERE email=?
|
||||||
DB --> Backend: AppUser + groups + permissions
|
DB --> Backend: AppUser + groups + permissions
|
||||||
Backend -> Backend: BCrypt.matches(password, hash)
|
Backend -> Backend: BCrypt.matches(password, hash)\n(timing-safe: dummy hash on miss)
|
||||||
Backend --> Frontend: 200 OK — UserDTO
|
Backend -> Backend: getSession(true).setAttribute(\n SPRING_SECURITY_CONTEXT, ctx)
|
||||||
Frontend -> Caddy: Set-Cookie: auth_token=<base64>\n(httpOnly, **Secure**, SameSite=strict, maxAge=86400)
|
Backend -> DB: INSERT spring_session\n+ spring_session_attributes
|
||||||
note right of Frontend
|
Backend -> Backend: AuditService.log(LOGIN_SUCCESS,\n {userId, ip, ua})
|
||||||
Secure flag is set because the
|
Backend --> Frontend: 200 OK — AppUser\nSet-Cookie: fa_session=<opaque>;\n Path=/; HttpOnly; SameSite=Strict; Secure
|
||||||
request scheme observed by the
|
Frontend -> Frontend: Parse Set-Cookie, re-emit fa_session\n(matches backend attrs)
|
||||||
app is https (forwarded by Caddy).
|
Frontend --> Caddy: 303 → /\nSet-Cookie: fa_session=<opaque>
|
||||||
end note
|
Caddy --> Browser: HTTPS 303 + Set-Cookie
|
||||||
Caddy -> Browser: HTTPS 200 + Set-Cookie
|
|
||||||
Browser -> Caddy: HTTPS GET / (next request)
|
== Authenticated request ==
|
||||||
Caddy -> Frontend: HTTP GET / + X-Forwarded-Proto: https
|
Browser -> Caddy: HTTPS GET /\nCookie: fa_session=<opaque>
|
||||||
Frontend -> Frontend: hooks.server.ts reads auth_token cookie
|
Caddy -> Frontend: HTTP GET / + Cookie + X-Forwarded-Proto: https
|
||||||
Frontend -> Backend: GET /api/users/me\nAuthorization: Basic <token>
|
Frontend -> Frontend: hooks.server.ts reads fa_session
|
||||||
Backend --> Frontend: 200 OK — user in event.locals
|
Frontend -> Backend: GET /api/users/me\nCookie: fa_session=<opaque>
|
||||||
Frontend --> Caddy: rendered page
|
Backend -> DB: SELECT * FROM spring_session\nWHERE SESSION_ID = ?
|
||||||
Caddy --> Browser: HTTPS 200
|
DB --> Backend: row (or null if expired)
|
||||||
|
alt Session valid
|
||||||
|
Backend -> DB: UPDATE spring_session\nSET LAST_ACCESS_TIME = now
|
||||||
|
Backend --> Frontend: 200 OK — AppUser
|
||||||
|
Frontend --> Caddy: rendered page
|
||||||
|
Caddy --> Browser: HTTPS 200
|
||||||
|
else Session expired (idle > 8h) or unknown
|
||||||
|
Backend --> Frontend: 401 Unauthorized
|
||||||
|
Frontend -> Frontend: hooks: delete fa_session cookie
|
||||||
|
Frontend --> Caddy: 302 → /login?reason=expired
|
||||||
|
Caddy --> Browser: HTTPS 302
|
||||||
|
end
|
||||||
|
|
||||||
|
== Logout ==
|
||||||
|
Browser -> Caddy: HTTPS POST /logout
|
||||||
|
Caddy -> Frontend: HTTP POST /logout\nCookie: fa_session=<opaque>
|
||||||
|
Frontend -> Backend: POST /api/auth/logout\nCookie: fa_session=<opaque>
|
||||||
|
Backend -> Backend: session.invalidate()\nSecurityContextHolder.clearContext()
|
||||||
|
Backend -> DB: DELETE FROM spring_session\nWHERE SESSION_ID = ?
|
||||||
|
Backend -> Backend: AuditService.log(LOGOUT,\n {userId, ip, ua})
|
||||||
|
Backend --> Frontend: 204 No Content
|
||||||
|
Frontend -> Frontend: cookies.delete('fa_session')
|
||||||
|
Frontend --> Caddy: 303 → /login
|
||||||
|
Caddy --> Browser: HTTPS 303 (cookie cleared)
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
|
|||||||
Reference in New Issue
Block a user