Skip to content

Authentication & Security

The Tech Strategy Tool uses cookie-based session authentication with role-based access control. This page covers the authentication flow, session management, CSRF protection, and security hardening measures.

The choice of cookie-based sessions over JWTs is deliberate:

Immediate role enforcement. When an administrator changes a user’s role or deletes their account, it must take effect quickly. With server-validated sessions, every request checks the user’s current state against the database (or a short-lived cache). With JWTs, the role is encoded in the token and the user continues to operate with the old role until the token expires.

Simplicity for a single-instance tool. There is no need for stateless authentication across multiple services. A single server validates a single session store. JWTs solve a distributed trust problem that does not exist here.

SSE connection authentication. SSE connections are long-lived HTTP connections. Cookie-based auth works naturally — the browser sends the cookie on connection open. JWTs in SSE would require either URL-embedded tokens (security risk) or custom header mechanisms that the EventSource API does not support natively.

PropertyValuePurpose
Nametechstrat_sessionSession identifier
HttpOnlytruePrevents JavaScript access (XSS mitigation)
SecuretrueCookie only sent over HTTPS
SameSiteStrictCookie not sent on cross-origin requests (CSRF mitigation)
Expiry7 daysSession lifetime

SessionAuthenticationHandler is a custom ASP.NET Core authentication handler registered as the default scheme. On each request:

  1. Read the techstrat_session cookie
  2. Check the SessionCache (in-memory ConcurrentDictionary with 5-minute TTL)
  3. If not cached, load the session from the database (eager-loads the associated User)
  4. Verify the session is not expired
  5. Build a ClaimsPrincipal with claims: NameIdentifier, Name, Role, SessionId

SessionCleanupService is a hosted service that runs every hour and deletes expired sessions from the database via ISessionRepository.DeleteExpiredAsync().

Three roles with hierarchical permissions:

RoleCapabilities
ViewerRead-only access to strategy content
EditorAll viewer capabilities + create and edit strategy content
AdminAll editor capabilities + manage users, teams, event log, restore

Three policies are defined in RolePolicies:

PolicyAllowed Roles
ViewerOrAboveviewer, editor, admin
EditorOrAboveeditor, admin
AdminOnlyadmin

These are applied to endpoint groups and individual endpoints. Some endpoints have additional role checks within the handler (e.g., team-level events and restore_history require admin role even though the events endpoint only requires EditorOrAbove).

CsrfMiddleware requires an X-CSRF-Token: 1 header on all mutating requests (POST, PUT, PATCH, DELETE) to /api/* paths. This is a simple CSRF mitigation:

  • Browsers cannot set custom headers on cross-origin requests without CORS preflight
  • The SameSite=Strict cookie policy provides a second layer of defense
  • Login (POST /api/auth/login) is exempt since no session exists yet

If the header is missing, the middleware returns 403 Forbidden.

LoginRateLimiter protects against brute-force password attacks:

  • Tracks failed login attempts per username using ConcurrentDictionary
  • After 5 failed attempts within 15 minutes, the account is locked out
  • Successful login clears the failure counter
  • The limiter is in-memory only — counters reset on application restart

PasswordHasher wraps ASP.NET Core’s PasswordHasher<string>, which uses PBKDF2 by default. On verification:

  • Success — password matches
  • SuccessRehashNeeded — password matches but was hashed with an older algorithm; treated as success (no rehash is performed)
  • Failed — password does not match
MeasureImplementation
AuthenticationCookie-based sessions with 7-day expiry
Session storagePostgreSQL with periodic cleanup
Password hashingASP.NET Core Identity PBKDF2
CSRF protectionCustom header requirement + SameSite=Strict cookies
XSS preventionHttpOnly cookies, HTML escaping in HighlightRenderer
Rate limitingPer-username lockout after 5 failures in 15 minutes
Input validationTwo-layer: HTTP layer validates envelope structure and admin/auth fields (Roles.All, UserFieldLimits); domain processor validates business rules and FieldLimits
AuthorizationRole-based policies on all endpoints
FilePurpose
src/TechStrat.Api/Auth/SessionAuthenticationHandler.csCookie-based session authentication
src/TechStrat.Api/Auth/SessionCache.csIn-memory session cache (5-min TTL)
src/TechStrat.Api/Auth/CsrfMiddleware.csCSRF header validation
src/TechStrat.Api/Auth/LoginRateLimiter.csBrute-force protection
src/TechStrat.Api/Auth/PasswordHasher.csPassword hash/verify wrapper
src/TechStrat.Api/Auth/RolePolicies.csAuthorization policy definitions (uses Roles.* constants)
src/TechStrat.Api/Auth/SessionCleanupService.csExpired session cleanup
src/TechStrat.Core/Auth/Roles.csRole constants (Viewer, Editor, Admin) and All set
src/TechStrat.Core/Auth/UserFieldLimits.csUsername/password length constraints