Password Policy
UniAuth enforces a comprehensive password policy designed to protect user accounts from brute-force attacks, credential stuffing, and password reuse. This guide explains each layer of protection, the password reset flow, email verification, and account lockout behavior.
Password Requirements
All passwords must meet the following baseline requirements:
- Minimum length: 8 characters
- Strength score: At least 2 ("fair") on a 0–4 scale
UniAuth does not enforce arbitrary complexity rules such as "must contain an uppercase letter, a number, and a symbol." Research shows that these rules lead to predictable substitution patterns (e.g., P@ssw0rd!) without meaningfully increasing security. Instead, UniAuth uses intelligent strength scoring that evaluates the actual difficulty of guessing a password.
Strength Scoring
Every password is evaluated against a comprehensive scoring algorithm that considers dictionary words, common patterns (dates, sequences, keyboard walks), repetition, and estimated crack time. The result is a score from 0 to 4:
| Score | Rating | Description |
|---|---|---|
| 0 | Too guessable | Risky password — easily guessed |
| 1 | Very guessable | Protection from throttled online attacks only |
| 2 | Somewhat guessable (minimum) | Protection from unthrottled online attacks |
| 3 | Safely unguessable | Moderate protection from offline attacks |
| 4 | Very unguessable | Strong protection from offline attacks |
The scoring is context-aware: passwords that contain the user's email address, first name, last name, or username are penalized. For example, a user named "John Smith" choosing johnsmith2024 would receive a lower score than a random string of the same length.
The API response includes actionable feedback when a password is too weak:
POST /api/auth/register
Content-Type: application/json
{
"email": "[email protected]",
"password": "john2024",
"firstName": "John"
}
// Response (400):
{
"error": "Password is too weak",
"score": 1,
"feedback": {
"warning": "This is similar to a commonly used password",
"suggestions": [
"Add another word or two — uncommon words are better",
"Avoid sequences or repeated characters"
]
}
}correct-horse-battery-staple typically score 4/4 and are easier to remember than short, complex passwords.Breach Detection
During registration and password changes, UniAuth checks whether the chosen password has appeared in known data breaches. This check uses the k-anonymity protocol: only the first 5 characters of the password's SHA-1 hash are sent to the breach database. The full password and its complete hash never leave UniAuth's servers.
If a password is found in breaches, UniAuth returns a non-blocking warning. The password is still accepted (assuming it meets the strength requirement), but the user is informed so they can make a better choice:
// Registration response with breach warning (200):
{
"success": true,
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "[email protected]"
},
"breachWarning": "This password has appeared in data breaches. Consider choosing a different password."
}The breach check has a 3-second timeout to avoid delaying registration if the external service is slow or unavailable. If the check times out, registration proceeds normally without a warning.
Password Reset Flow
UniAuth provides a secure, token-based password reset flow. The process involves four steps:
Step 1: Request a Reset
The user submits their email address to request a password reset link:
POST /api/auth/forgot-password
Content-Type: application/json
{
"email": "[email protected]"
}
// Response (200) — always returns success:
{
"success": true,
"message": "If an account exists with that email, a reset link has been sent."
}Step 2: Email Delivery
If the account exists, UniAuth sends an email containing a unique, time-limited reset link. The link points to your application's password reset page with a cryptographic token as a query parameter. The token is hashed before storage, so even if the database were compromised, the token could not be used.
Step 3: Validate the Token
Before showing the password reset form, validate that the token is still valid:
POST /api/auth/validate-reset-token
Content-Type: application/json
{
"token": "abc123def456..."
}
// Valid token (200):
{
"valid": true
}
// Expired or invalid token (400):
{
"error": "Invalid or expired reset token"
}Step 4: Set the New Password
Submit the token along with the new password. The new password must pass the same strength requirements as during registration:
POST /api/auth/reset-password
Content-Type: application/json
{
"token": "abc123def456...",
"password": "newSecurePassword!2024"
}
// Success (200):
{
"success": true,
"message": "Password has been reset successfully"
}
// Weak password (400):
{
"error": "Password is too weak",
"score": 1,
"feedback": {
"warning": "This is a top-100 common password",
"suggestions": ["Add another word or two — uncommon words are better"]
}
}Upon successful reset, all existing sessions for the user are invalidated, requiring the user to sign in again with their new password. The reset token is also consumed and cannot be reused.
Email Verification
After registration, users must verify their email address before signing in. UniAuth automatically sends a verification email containing a unique link. When the user clicks the link, your application should call the verification endpoint:
POST /api/auth/verify-email
Content-Type: application/json
{
"token": "verification-token-from-email-link"
}
// Success (200):
{
"success": true,
"message": "Email verified successfully"
}
// Invalid or expired token (400):
{
"error": "Invalid or expired verification token"
}Resending Verification Email
If the verification email was lost or expired, users can request a new one:
POST /api/auth/resend-verification
Content-Type: application/json
{
"email": "[email protected]"
}
// Response (200):
{
"success": true,
"message": "If your email is registered and unverified, a new verification email has been sent."
}Like the password reset endpoint, the resend endpoint returns a generic success message regardless of whether the email exists, preventing enumeration attacks.
Account Lockout
UniAuth implements progressive account lockout to protect against brute-force and credential-stuffing attacks. After a threshold of failed login attempts, the account is temporarily locked for an increasing duration:
| Failed Attempts | Lockout Duration |
|---|---|
| 5 | 1 minute |
| 10 | 5 minutes |
| 15 | 15 minutes |
| 20+ | 1 hour |
When a locked account attempts to sign in, the API returns HTTP 423 with the remaining lockout time:
POST /api/auth/login
Content-Type: application/json
{
"email": "[email protected]",
"password": "wrongpassword"
}
// Response (423 Locked):
{
"error": "Account is temporarily locked",
"remainingTime": 245,
"message": "Too many failed attempts. Please try again in 4 minutes and 5 seconds."
}The lockout counter resets automatically when:
- The user signs in successfully with the correct password
- The user authenticates using a passkey
- An administrator manually unlocks the account from the Admin Dashboard
Email Enumeration Prevention
Email enumeration is an attack where an adversary probes your authentication endpoints to determine which email addresses are registered. UniAuth prevents this by returning identical responses regardless of whether an email exists:
- Forgot password: Always returns
"If an account exists with that email, a reset link has been sent." - Resend verification: Always returns
"If your email is registered and unverified, a new verification email has been sent." - Login: Returns a generic
"Invalid email or password"for both non-existent accounts and incorrect passwords.
Response timing is also consistent across both cases to prevent timing-based enumeration. This design means your users' email addresses remain private even if an attacker targets these endpoints.
Best Practices
To maximize account security, we recommend the following practices for your users:
- Use a password manager. Tools like 1Password, Bitwarden, or the built-in browser password manager generate and store strong, unique passwords for every account.
- Enable multi-factor authentication (MFA). Even the strongest password can be compromised through phishing or data breaches. Adding a second factor (TOTP, email, or SMS) provides an additional layer of protection. See the MFA documentation.
- Never reuse passwords. If one service is breached, reused credentials can be used to access other accounts through credential stuffing attacks.
- Check for breaches regularly. If you receive a breach warning during registration or password change, choose a different password immediately. You can also check your email at haveibeenpwned.com.
- Consider using passkeys. Passkeys provide phishing-resistant authentication that eliminates passwords entirely. See the Authentication documentation for setup instructions.
- Prefer passphrases over complex passwords. A phrase like
meadow-sunset-bicycle-thunderis both stronger and easier to remember thanP@55w0rd!.
For Developers: Implementing Password Validation
If you are building a registration or password change form that connects to UniAuth, here is how to handle the strength validation response:
// JavaScript / TypeScript example
async function registerUser(email: string, password: string, firstName: string) {
const response = await fetch('https://uniauth.id/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password, firstName }),
});
const data = await response.json();
if (!response.ok) {
if (data.score !== undefined) {
// Password too weak — show feedback to the user
console.log('Score:', data.score, '/ 4');
console.log('Warning:', data.feedback?.warning);
console.log('Suggestions:', data.feedback?.suggestions);
} else {
// Other error (email taken, validation error, etc.)
console.error(data.error);
}
return null;
}
// Registration succeeded
if (data.breachWarning) {
// Show non-blocking warning about breach
console.warn(data.breachWarning);
}
return data.user;
}# Python example
import requests
def register_user(email: str, password: str, first_name: str) -> dict | None:
response = requests.post(
"https://uniauth.id/api/auth/register",
json={"email": email, "password": password, "firstName": first_name},
)
data = response.json()
if response.status_code != 200:
if "score" in data:
print(f"Password score: {data['score']} / 4")
print(f"Warning: {data.get('feedback', {}).get('warning')}")
print(f"Suggestions: {data.get('feedback', {}).get('suggestions')}")
else:
print(f"Error: {data.get('error')}")
return None
if "breachWarning" in data:
print(f"Warning: {data['breachWarning']}")
return data["user"]<?php
// PHP example
function registerUser(string $email, string $password, string $firstName): ?array {
$ch = curl_init('https://uniauth.id/api/auth/register');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'email' => $email,
'password' => $password,
'firstName' => $firstName,
]),
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($httpCode !== 200) {
if (isset($data['score'])) {
echo "Password score: {$data['score']} / 4\n";
echo "Warning: " . ($data['feedback']['warning'] ?? '') . "\n";
echo "Suggestions: " . implode(', ', $data['feedback']['suggestions'] ?? []) . "\n";
} else {
echo "Error: {$data['error']}\n";
}
return null;
}
if (isset($data['breachWarning'])) {
echo "Warning: {$data['breachWarning']}\n";
}
return $data['user'];
}API Reference Summary
| Endpoint | Method | Description |
|---|---|---|
/api/auth/register | POST | Register a new account (validates password strength and checks breaches) |
/api/auth/login | POST | Sign in (enforces lockout policy) |
/api/auth/forgot-password | POST | Request a password reset email |
/api/auth/validate-reset-token | POST | Check if a reset token is still valid |
/api/auth/reset-password | POST | Set a new password using a valid reset token |
/api/auth/verify-email | POST | Verify an email address with a token |
/api/auth/resend-verification | POST | Resend the email verification link |