Skip to content

Documentation

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).

7/20/2026
5 min read Edit on GitHub

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) using verifyOAuthState. 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, lockoutUntil is checked. Active lockouts return 403 with remaining time.
  • Failed attempt tracking: Each failed attempt increments failedAttempts. At 5 failures, lockoutUntil is set to now + 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 with gt(expires, now), then diagnose.
  • Mongo: claim filter includes expires: { $gt: new Date() }.
  • UI: sign-in.svelte handleResetSubmit maps result.code to toasts (no false success toast).
  • Remote: auth.remote.ts resetPWInternal forwards code to the client.
  • E2E: tests/e2e/routes/login/extended-auth.spec.tsseed-expired-password-reset testing action seeds a past-expiry password_reset token; 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-code input field using tick() when Two-Factor Authentication is required (see 2FA documentation).

  • A5/P7: Implemented prefers-reduced-motion CSS media queries to disable animations (e.g., the .wiggle effect) 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-500 to text-surface-600 dark:text-surface-400.

  • A9: Enhanced visual focus indicators by adding :focus-visible outline styles to the main section.

  • A10: Added <svelte:head><meta name="robots" content="noindex, nofollow" /></svelte:head> to +page.svelte to 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-For or X-Real-IP headers (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 user
  • DELETE /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_USER on public routes with read-only guest role — 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.

Related

securityauthenticationrate-limitingoauthaccessibility
Was this page helpful?