Login Security
Implementation details for IP resolution, rate limiter secrets, OAuth HMAC validation, session tracking, accessibility fixes, and new auth methods (API Keys, Magic Links, Guest Auth, WebAuthn).
On this page
This document details how we have implemented the best A++ security for the SveltyCMS authentication flow, along with essential accessibility improvements.
Security Improvements
1. Audit Log IP Resolution
Audit logs reliably resolve the client IP address using X-Forwarded-For / X-Real-IP request headers in all authentication flows (login, password reset, and password forgotten). Additionally, the session record itself captures the IP address and User-Agent at creation time for the Active Devices feature (see Session & Device Tracking below).
2. OAuth State HMAC Verification
The Google OAuth flow (google-auth.ts) has been hardened using HMAC-SHA256 signatures for the state parameter.
- Generation: The state payload is signed during
generateGoogleAuthUrl. - Verification: The signature is verified on the callback (
signInOAuth) usingverifyOAuthState. This prevents OAuth state manipulation and ensures the integrity of the invitation token during the OAuth flow.
3. Separate Rate Limiter Secret
The RateLimiter cookie signature has been decoupled from the primary JWT_SECRET_KEY. It now uses RATE_LIMIT_SECRET with a secure fallback (JWT_SECRET_KEY + "-ratelimit"). This compartmentalization prevents a compromised rate-limit secret from jeopardizing the entire authentication system.
4. RFC 6585 Compliance (Retry-After Header)
All endpoints utilizing the RateLimiter now correctly inject the Retry-After: 60 HTTP header upon returning a 429 Too Many Requests status, ensuring compliance with RFC 6585 and properly signaling clients to back off.
5. Demo Tenant Capacity Limit
To prevent resource exhaustion during demo environments, a strict cap of 100 users per tenant is enforced during the signUp action. This leverages the getUserCount method from the authentication adapter to provide an early exit if the limit is reached.
6. Account Lockout & Brute-Force Prevention
Accounts are locked for 15 minutes after 5 consecutive failed login attempts. This is enforced in AuthNamespace.login() (src/services/sdk/namespaces/auth-namespace.ts) which gates the REST and GraphQL login paths, and redundantly in Auth.authenticate() (src/databases/auth/index.ts) for direct database authentication calls.
- Lockout check: Before password verification,
lockoutUntilis checked. Active lockouts return 403 with remaining time. - Failed attempt tracking: Each failed attempt increments
failedAttempts. At 5 failures,lockoutUntilis set tonow + 15 minutes. - Auto-reset: Lockout state is cleared on successful login or when the lockout expires.
- Consistent error messages: Both “user not found” and “wrong password” return identical
"Invalid credentials"to prevent user enumeration.
7. Password-Reset Token Expiry (Diagnosable)
Auth.consumeToken() / adapter consumeToken() refuse expired tokens (expires > now on claim). Failures return structured codes for the login UI:
| Code | UI toast |
|---|---|
TOKEN_EXPIRED |
“This reset link has expired. Request a new one…” |
TOKEN_ALREADY_CONSUMED |
“This reset link was already used…” |
TOKEN_NOT_FOUND |
Generic invalid link |
- SQL:
relational-auth.ts— atomic update withgt(expires, now), then diagnose. - Mongo: claim filter includes
expires: { $gt: new Date() }. - UI:
sign-in.sveltehandleResetSubmitmapsresult.codeto toasts (no false success toast). - Remote:
auth.remote.tsresetPWInternalforwardscodeto the client. - E2E:
tests/e2e/routes/login/extended-auth.spec.ts—seed-expired-password-resettesting action seeds a past-expirypassword_resettoken; submit asserts Link expired toast (no soft-skip).
Accessibility Fixes (WCAG 2.2 AA)
To ensure the authentication pages are accessible to all users, the following fixes were implemented on sign-in.svelte and sign-up.svelte:
-
A1: Removed
role="button"from the main<section>element. -
A2: Added programmatic focus to the
#twofa-codeinput field usingtick()when Two-Factor Authentication is required (see 2FA documentation). -
A5/P7: Implemented
prefers-reduced-motionCSS media queries to disable animations (e.g., the.wiggleeffect) for users who prefer reduced motion. -
A6: Added skip links (
<a href="#signin-form" class="sr-only focus:not-sr-only">Skip to form</a>) at the top of the authentication forms for keyboard navigation. -
A8: Improved contrast for hint texts by changing
text-surface-500totext-surface-600 dark:text-surface-400. -
A9: Enhanced visual focus indicators by adding
:focus-visibleoutline styles to the main section. -
A10: Added
<svelte:head><meta name="robots" content="noindex, nofollow" /></svelte:head>to+page.svelteto prevent search engines from indexing the login page.
Session & Device Tracking
SveltyCMS captures device information at session creation for security auditing and the Active Devices UI.
Device Info Capture
When a user logs in, the following data is captured and stored with the session record:
- User-Agent: Full browser and OS information (e.g.,
Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/...) - IP Address: Resolved from
X-Forwarded-FororX-Real-IPheaders (proxy-aware)
Active Devices UI
Users can view and manage their active sessions from the User Settings page (API-ready; UI component pending):
- View: List of all active sessions with device info, last activity time, and current session indicator
- Revoke: One-click session termination that purges the session from all 3 cache layers (memory, Redis, database)
- Auto-Cleanup: Sessions not belonging to the password-changer are automatically invalidated on password change
API Endpoints
GET /api/user/sessions— List all active sessions for the authenticated userDELETE /api/user/sessions/:sessionId— Revoke a specific session across all cache layers
Auth Methods
The SveltyCMS auth surface has been expanded with 4 additional authentication methods. See the Authentication System Architecture for full details and security considerations:
- API Keys (
sck_*): Machine-to-machine bearer tokens with SHA-256 hashed storage, scoped permissions, admin REST API, and instant cache invalidation on revoke. - Magic Links: Passwordless email login via single-use tokens with 15-min TTL, TOCTOU-safe consumption, and anti-enumeration responses.
- Guest Auth: Ephemeral
ANONYMOUS_USERon public routes with read-onlyguestrole — stateless, no session created. - WebAuthn/Passkeys (~75%): Biometric login via platform authenticators — challenge/response ceremony complete, COSE→JWK attestation, CBOR parsing. Settings-page UI pending.