Security Architecture
VibeLogin handles authentication so you don't have to think about security details. This page explains how things work under the hood for teams that want to understand the design before adopting it.
Password hashing
Passwords are hashed with Argon2id, the winner of the Password Hashing Competition and the current OWASP recommendation.
| Algorithm | Argon2id |
| Memory cost | 19 MiB |
| Iterations | 2 |
| Parallelism | 1 |
| Output length | 32 bytes (256 bits) |
Verification uses constant-time comparison to prevent timing attacks. Raw passwords are never logged or stored.
Token model
VibeLogin uses a short-lived access token + long-lived refresh token model. Both are JWTs.
| Access token lifetime | 15 minutes (configurable) |
| Refresh token lifetime | 7 days (configurable) |
| Signing algorithm | RS256 (RSA-2048 + SHA-256) |
| Key isolation | Each project gets its own RSA key pair |
| Token refresh | Silent, automatic via SDK middleware |
Access token claims
{
"sub": "user-uuid", // User ID
"sid": "session-uuid", // Session ID (revocation handle)
"jti": "token-uuid", // Unique token ID
"role": "member", // User role at issuance time
"iss": "https://api.vibelogin.com",
"aud": "project-id", // Prevents cross-project reuse
"iat": 1712000000,
"exp": 1712000900
}The payload is intentionally minimal — no PII like email or name. This keeps tokens small and avoids leaking user data through CDN logs or browser storage.
Session management
Each login creates a server-side session record linked to the user. The refresh token is the session handle.
- Refresh tokens are one-time use. Every refresh rotates the token and invalidates the old one.
- Only the SHA-256 hash of the refresh token is stored. A database breach does not expose valid tokens.
- IP address and User-Agent are captured at session creation for anomaly detection.
- Session revocation is instant — deleting the session record invalidates all tokens for that session.
Cookie security
The SDK stores tokens in cookies, not localStorage. This protects against XSS attacks.
| httpOnly | true — JavaScript cannot read tokens |
| secure | true in production (HTTPS only) |
| sameSite | lax — prevents CSRF on cross-origin POST |
| path | / (root-scoped) |
| domain | Configurable for subdomain sharing |
Cookie names
{prefix}-access— short-lived access token{prefix}-refresh— long-lived refresh tokenvibelogin_state— OAuth/login CSRF state (10-minute TTL)
Encryption at rest
Project signing keys (RSA private keys) are encrypted before storage using AES-256-GCM.
| Algorithm | AES-256-GCM |
| IV | 96-bit, randomly generated per encryption |
| Auth tag | 128-bit (integrity verification) |
| Key derivation | HKDF-SHA256 with purpose-specific context |
Each encryption context (e.g. signing key storage) derives a unique key from the master secret, so a compromise in one context doesn't affect others.
JWKS and key verification
Each project exposes a public JWKS endpoint that the SDK uses to verify access tokens without calling the VibeLogin API on every request.
GET https://api.vibelogin.com/v1/jwks/{projectId}- Public keys are cached for 1 hour (server and SDK)
- Key ID (
kid) in the JWT header maps to the correct key - Audience (
aud) claim prevents cross-project token reuse
CSRF protection
The OAuth and redirect login flows use a state parameter to prevent CSRF attacks.
- A cryptographically random state value is generated for each login
- The state is stored in a short-lived cookie (10-minute TTL)
- On callback, the state from the URL must match the cookie value
- State is single-use — deleted immediately after validation
For embedded mode API calls, the SDK includes automatic CSRF token handling.
Rate limiting
All authentication endpoints are rate-limited per IP address to prevent brute-force and abuse.
| Endpoint | Limit | Window |
|---|---|---|
| Sign in / Sign up | 10 requests | 15 minutes |
| Magic link / Password reset | 5 requests | 15 minutes |
| Token refresh | 30 requests | 15 minutes |
Rate-limited responses return standard 429 status with Retry-After, X-RateLimit-Limit, and X-RateLimit-Remaining headers.
Security headers
The VibeLogin API sets these headers on every response:
| Strict-Transport-Security | max-age=31536000; includeSubDomains |
| X-Content-Type-Options | nosniff |
| X-Frame-Options | DENY |
| Referrer-Policy | strict-origin-when-cross-origin |
| Permissions-Policy | camera=(), microphone=(), geolocation=() |
Anti-enumeration
Auth endpoints are designed to prevent user enumeration:
- Signup performs a dummy Argon2 hash for existing emails so the response time is identical whether the account exists or not.
- Error messages use generic language that does not reveal whether an email is registered.
- Rate limiting is per-IP, not per-email, to prevent enumeration through error responses.
Redirect vs embedded security
The two integration modes have different security profiles:
- Redirect mode: Login happens on VibeLogin's hosted pages. Your app never touches passwords. Tokens are delivered via a server-side callback. This is the more secure option.
- Embedded mode: Login forms render in your app. Passwords are sent directly from your frontend to the VibeLogin API over HTTPS. CSRF protection is automatic via the SDK.
We recommend redirect mode for production apps. It supports the full feature set and keeps credentials off your domain entirely.