Authentication
UniAuth provides multiple authentication methods, from traditional email/password to modern passkeys and social login. All methods are designed with security best practices including progressive lockout, breach detection, and post-quantum session signatures.
Email & Password Authentication
The primary authentication method uses email and password credentials. Passwords are hashed using Argon2id (via @node-rs/argon2) with the following parameters:
- Memory cost: 64 MB
- Time cost: 3 iterations
- Parallelism: 4 lanes
Legacy bcrypt hashes are transparently migrated to Argon2id on successful login, so existing users are upgraded without any action required.
Registration
New users register via POST /api/auth/register with their email, password, and optional name fields. The password is validated against:
- zxcvbn strength check — Must score at least 2/4. The check is context-aware: it penalizes passwords that contain the user's email or name.
- HaveIBeenPwned breach check — Uses k-anonymity to safely check if the password has appeared in known breaches. This is a non-blocking warning (3-second timeout).
After registration, a verification email is sent. The user must click the verification link before certain features become available.
Login Flow
POST /api/auth/login
Content-Type: application/json
{
"email": "[email protected]",
"password": "securePassword123!"
}
// Success response (200):
{
"success": true,
"user": {
"id": "uuid",
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe"
}
}
// If 2FA is enabled (200, requires second step):
{
"success": true,
"requires2FA": true,
"userId": "uuid",
"defaultMethod": "totp",
"availableMethods": ["totp", "email"]
}On successful login, an HTTP-only, secure, SameSite cookie (auth_token) is set with a 7-day expiration. The JWT payload contains the user's ID, email, name, avatar URL, and role. Sessions are also fingerprinted using a SHA-256 hash of the client's IP address and User-Agent string.
Threat Detection
Every login attempt is scored by the statistical threat detection system. Risk factors include:
- Login from a new IP address not previously seen for this user
- Login from a new User-Agent / device
- Login at unusual hours (outside the user's historical pattern)
- Recent failed login attempts
- Burst detection (many rapid login attempts)
Per-user baselines are stored in the users.metadata.login_stats JSONB field and updated after each login.
Social Login (Google & GitHub)
UniAuth supports OAuth-based social login with Google and GitHub. Both providers use PKCE (S256 code challenge) for security. The flow works as follows:
- The user clicks "Sign in with Google" or "Sign in with GitHub" on the login page.
- The client calls
POST /api/auth/oauth/initiatewith the provider name. - The server generates a CSRF state token and PKCE code_verifier, stores them in the user's metadata, and returns the authorization URL.
- The user is redirected to the provider's consent screen.
- After approval, the provider redirects back to
/api/auth/oauth/callbackwith an authorization code. - The server verifies the state, exchanges the code for tokens, fetches the user profile, and either logs in an existing user or creates a new account.
- The provider's access token is encrypted with AES-256-GCM before storage in the
connected_servicestable.
Configuration
To enable social login, set the following environment variables:
# Google OAuth
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-google-client-secret
# GitHub OAuth
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secretThe callback URL to configure in your Google/GitHub OAuth app settings is:
https://your-domain.com/api/auth/oauth/callbackWebAuthn / Passkeys
UniAuth supports WebAuthn passkeys for passwordless authentication. This uses the @simplewebauthn/server and @simplewebauthn/browser (v10) libraries with direct attestation.
Registering a Passkey
- The authenticated user navigates to their security settings.
- The client calls
POST /api/auth/passkey/register-optionsto get registration options (including a challenge stored in the user's metadata). - The browser's WebAuthn API prompts the user to create a credential (e.g., fingerprint, Face ID, hardware key).
- The client sends the attestation response to
POST /api/auth/passkey/register-verify. - The server verifies the attestation and stores the credential in the
passkeystable.
Authenticating with a Passkey
- The client calls
POST /api/auth/passkey/authenticate-optionsto get authentication options. - The browser prompts the user to use their passkey.
- The client sends the assertion response to
POST /api/auth/passkey/authenticate-verify. - The server verifies the assertion, updates the credential counter, and creates a session.
Conditional UI (Passkey Autofill)
UniAuth supports WebAuthn Conditional UI, which allows passkeys to appear in the browser's autofill suggestions alongside saved passwords. When a user focuses the email field on the login page, the browser may display available passkeys. This provides a seamless login experience without the user having to click a separate "Sign in with Passkey" button.
The Relying Party (RP) ID is derived from the HOST environment variable. Make sure this is set correctly for passkeys to work across your domain.
Multi-Factor Authentication (2FA)
UniAuth supports multiple 2FA methods that can be enabled simultaneously. Users can choose their default method and switch between available methods during verification.
TOTP (Authenticator Apps)
Time-based One-Time Passwords work with any standard authenticator app (Google Authenticator, Authy, 1Password, etc.). Setup flow:
- User calls
POST /api/auth/2fa/setupwith{ "method": "totp" }. - Server generates a TOTP secret, encrypts it with AES-256-GCM, and returns a QR code URI.
- User scans the QR code with their authenticator app.
- User enters the 6-digit code to verify setup via
POST /api/auth/2fa/verify. - 2FA is now active. Future logins require the TOTP code after password verification.
Email OTP
A 6-digit code is sent to the user's verified email address. The code is encrypted with AES-256-GCM before storage in the otp_tokens table and expires after a short validity window. To send a code during login:
POST /api/auth/2fa/send-code
Content-Type: application/json
{
"userId": "uuid",
"method": "email"
}SMS OTP (via Twilio)
Similar to email OTP, but the code is sent via SMS using the Twilio API. Requires Twilio credentials to be configured:
TWILIO_ACCOUNT_SID=your-twilio-sid
TWILIO_AUTH_TOKEN=your-twilio-auth-token
TWILIO_PHONE_NUMBER=+1234567890The user's phone number must be verified before SMS 2FA can be enabled. Phone verification uses the same OTP mechanism.
Default Method Selection
When a user has multiple 2FA methods enabled, they can set a default via:
POST /api/auth/2fa/set-default
Content-Type: application/json
{
"method": "totp" // or "email" or "sms"
}During login, the default method is used first, but the user can switch to any other enabled method.
2FA Verification During Login
POST /api/auth/2fa/verify
Content-Type: application/json
{
"userId": "uuid",
"code": "123456",
"method": "totp"
}Account Lockout
UniAuth implements progressive account lockout to protect against brute-force attacks. The lockout duration increases with the number of failed attempts:
| Failed Attempts | Lockout Duration |
|---|---|
| 5 | 1 minute |
| 10 | 5 minutes |
| 15 | 15 minutes |
| 20+ | 1 hour |
When an account is locked, the API returns HTTP 423 with the remaining lockout time. The counter resets on successful login or passkey authentication. Lockout state is stored in the failed_login_attempts and locked_until columns on the users table.
Session Management
Sessions are managed with multiple layers of security:
Session Lifecycle
- Inactivity timeout: Sessions expire after 24 hours of inactivity. The
last_activity_attimestamp is updated on each request viatouchSession(). - Concurrent session limit: A maximum of 10 active sessions per user. When the limit is reached, the oldest session is automatically revoked.
- Password-change revocation: When a user changes their password, all other sessions are immediately invalidated via
invalidateOtherSessions().
Session Fingerprinting
Each session is fingerprinted using a SHA-256 hash of the client's IP address and User-Agent. This fingerprint is stored in the fingerprint_hash column. If a subsequent request's fingerprint does not match the stored value, the system flags a potential session hijack.
Post-Quantum Session Signatures
Sessions are signed using ML-DSA-44 (FIPS 204) post-quantum digital signatures. The server maintains a keypair (generated at startup and stored encrypted in system_settings). Each session token includes a PQC signature that can be verified to ensure the session was genuinely issued by this server instance.
Viewing and Revoking Sessions
Users can view their active sessions and revoke individual sessions or all sessions at once:
// List active sessions
GET /api/user/sessions
// Revoke all sessions except current
POST /api/user/sessions/revoke-allData Retention
UniAuth automatically cleans up expired data via a scheduled task that runs every 24 hours (initialized in instrumentation.ts):
- Expired sessions: purged after 30 days
- Expired tokens (password reset, email verification): purged after 7 days
- Expired OTP codes: purged after 24 hours
- Old activity logs: purged after 1 year