UI Authentication
This document describes how the UI generator handles authentication for generated HTMX web applications.
Architecture
The UI uses session-based authentication where the UI server owns sessions and the API remains stateless:
┌─────────────┐ session_id cookie ┌─────────────┐ Bearer token ┌─────────────┐
│ Browser │ ◄─────────────────────► │ UI Server │ ◄──────────────► │ API Server │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
Session Store
(in-memory)
Why this pattern:
- API stays stateless (12-factor app, horizontal scaling)
- Session revocation without token blacklists
- Cookie contains session ID, not token (more secure, smaller)
- Natural for SSR apps (like HTMX)
Session Flow
-
Registration: User submits registration form → UI calls API’s
createUser→ API returns user → UI extracts token (.tokenfield if present, otherwise.id) → UI creates session → Setssession_idcookie → Redirects to home -
Subsequent requests: UI reads
session_idcookie → Looks up session → Passes token to HTTP client → API validates token -
Logout: UI destroys session → Clears cookie → Redirects to login
Generated Files
When the schema has operations requiring authentication, the generator produces:
src/session.ts
Session store interface and in-memory implementation:
Sessioninterface with id, token, userId, userName, expiresAtSessionStoreinterface with create/get/destroy methodscreateInMemorySessionStore()factory
src/layout.ts
Extended with:
AuthStatetype for tracking authentication- Auth nav showing Login/Logout links based on state
- User name display when authenticated
src/pages.ts
Auth pages:
loginPage(error?)- Login form with optional error messageregisterPage(error?)- Registration form (ifcreateUseroperation exists)
src/index.ts
Session management and auth routes:
- Session cookie handling (
getSessionId,setSessionCookie,clearSessionCookie) AuthStatetype andgetAuthState(req)helpercreateClientForRequest(req)- Creates HTTP client with token from session/loginroute (GET shows form, POST authenticates)/logoutroute (destroys session)/registerroute (ifcreateUseroperation exists)
Auth Detection
The generator detects authentication requirements:
- Operation requires auth: Any operation with pre-invariants referencing
context.currentUser - Login operation: Operation with sensitive (password) input, name includes “login/signin/authenticate”
- Register operation: Operation with sensitive input, name includes “create” + “user”
Injectable Parameters
Parameters that are auto-filled from the auth context (like userId) are:
- Hidden from forms: Not rendered in the UI
- Not passed to client: The API injects them based on the authenticated user
This is detected via conditionReferencesCurrentUser() on operation invariants.
API Strategies
The UI works with any API auth strategy:
- SimpleBearerStrategy: User ID is used as token
- JwtStrategy: JWT is stored as token in session
- ApiKeyStrategy: API key is stored as token
The UI doesn’t know which strategy the API uses - it just stores whatever token it receives.
Session Store Options
Currently supports:
- In-memory store: For development and single-instance deployments
Future options (not yet implemented):
- Redis store: For production multi-instance deployments
Configuration
No special configuration needed. Authentication is automatically enabled when:
- Schema has operations with auth requirements (via invariants)
- Schema has a
createUseroperation (enables registration)
Login Convention
The generator detects login operations by checking for operations with sensitive (password) input whose name matches “login”, “signin”, or “authenticate”. When found, the login page calls that operation directly.
When no login operation is detected but a register operation exists, the generated UI falls back to calling client.login() — a hardcoded method on the generated HTTP client that calls POST /auth/login. The API must provide this endpoint (typically via the auth-password extension’s login route).
Limitations
-
Hardcoded
/auth/loginfallback: When the schema has no explicit login operation, login depends on the/auth/loginAPI endpoint existing. This is provided by the auth-password extension but is not enforced by the schema. -
Single session duration: Sessions expire after 24 hours (hardcoded in the generated session store).
-
No session persistence: In-memory sessions are lost on server restart.
Future Enhancements
See TODO.md for planned P3 items:
- UI account settings page
- UI delete account flow
- Pluggable schema components for standard auth patterns