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.
Authentication Flow
Section titled “Authentication Flow”Why Cookie-Based Sessions?
Section titled “Why Cookie-Based Sessions?”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.
Session Management
Section titled “Session Management”Cookie properties
Section titled “Cookie properties”| Property | Value | Purpose |
|---|---|---|
| Name | techstrat_session | Session identifier |
| HttpOnly | true | Prevents JavaScript access (XSS mitigation) |
| Secure | true | Cookie only sent over HTTPS |
| SameSite | Strict | Cookie not sent on cross-origin requests (CSRF mitigation) |
| Expiry | 7 days | Session lifetime |
Session authentication handler
Section titled “Session authentication handler”SessionAuthenticationHandler is a custom ASP.NET Core authentication handler registered as the default scheme. On each request:
- Read the
techstrat_sessioncookie - Check the
SessionCache(in-memoryConcurrentDictionarywith 5-minute TTL) - If not cached, load the session from the database (eager-loads the associated
User) - Verify the session is not expired
- Build a
ClaimsPrincipalwith claims:NameIdentifier,Name,Role,SessionId
Session cleanup
Section titled “Session cleanup”SessionCleanupService is a hosted service that runs every hour and deletes expired sessions from the database via ISessionRepository.DeleteExpiredAsync().
Role-Based Access Control
Section titled “Role-Based Access Control”Three roles with hierarchical permissions:
| Role | Capabilities |
|---|---|
| Viewer | Read-only access to strategy content |
| Editor | All viewer capabilities + create and edit strategy content |
| Admin | All editor capabilities + manage users, teams, event log, restore |
Authorization policies
Section titled “Authorization policies”Three policies are defined in RolePolicies:
| Policy | Allowed Roles |
|---|---|
ViewerOrAbove | viewer, editor, admin |
EditorOrAbove | editor, admin |
AdminOnly | admin |
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).
CSRF Protection
Section titled “CSRF Protection”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=Strictcookie 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.
Login Rate Limiting
Section titled “Login Rate Limiting”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
Password Hashing
Section titled “Password Hashing”PasswordHasher wraps ASP.NET Core’s PasswordHasher<string>, which uses PBKDF2 by default. On verification:
Success— password matchesSuccessRehashNeeded— password matches but was hashed with an older algorithm; treated as success (no rehash is performed)Failed— password does not match
Security Summary
Section titled “Security Summary”| Measure | Implementation |
|---|---|
| Authentication | Cookie-based sessions with 7-day expiry |
| Session storage | PostgreSQL with periodic cleanup |
| Password hashing | ASP.NET Core Identity PBKDF2 |
| CSRF protection | Custom header requirement + SameSite=Strict cookies |
| XSS prevention | HttpOnly cookies, HTML escaping in HighlightRenderer |
| Rate limiting | Per-username lockout after 5 failures in 15 minutes |
| Input validation | Two-layer: HTTP layer validates envelope structure and admin/auth fields (Roles.All, UserFieldLimits); domain processor validates business rules and FieldLimits |
| Authorization | Role-based policies on all endpoints |
Key Files
Section titled “Key Files”| File | Purpose |
|---|---|
src/TechStrat.Api/Auth/SessionAuthenticationHandler.cs | Cookie-based session authentication |
src/TechStrat.Api/Auth/SessionCache.cs | In-memory session cache (5-min TTL) |
src/TechStrat.Api/Auth/CsrfMiddleware.cs | CSRF header validation |
src/TechStrat.Api/Auth/LoginRateLimiter.cs | Brute-force protection |
src/TechStrat.Api/Auth/PasswordHasher.cs | Password hash/verify wrapper |
src/TechStrat.Api/Auth/RolePolicies.cs | Authorization policy definitions (uses Roles.* constants) |
src/TechStrat.Api/Auth/SessionCleanupService.cs | Expired session cleanup |
src/TechStrat.Core/Auth/Roles.cs | Role constants (Viewer, Editor, Admin) and All set |
src/TechStrat.Core/Auth/UserFieldLimits.cs | Username/password length constraints |