API Reference#
All endpoints return JSON with this standard shape:
{
"success": true | false,
"message": "Human-readable message",
"data": { ... } | null
}On error, the response additionally includes a machine-readable errorCode:
{
"success": false,
"message": "Invalid credentials",
"errorCode": "INVALID_CREDENTIALS"
}All auth routes are mounted at /auth. MFA routes are at /auth/mfa. OAuth routes are at /auth/oauth.
Authentication Endpoints#
/auth/registerRequest Body#
| Field | Type | Rules |
|---|---|---|
fullname | string | Required, non-empty |
email | string | Required, valid email format |
password | string | 8–128 chars, 1 uppercase, 1 lowercase, 1 digit, 1 special char |
curl -X POST http://localhost:5000/auth/register \
-H "Content-Type: application/json" \
-d '{
"fullname": "John Doe",
"email": "john@example.com",
"password": "MyP@ssw0rd!"
}'{
"success": true,
"message": "Registration successful. Please verify your email.",
"data": {
"id": "cm...",
"fullname": "John Doe",
"email": "john@example.com",
"isVerified": false,
"mfaEnabled": false,
"createdAt": "2025-01-15T10:30:00.000Z"
}
}5 requests per 10 minutes per IP. Returns 429 with a retry-after header when exceeded.
/auth/loginRequest Body#
| Field | Type | Rules |
|---|---|---|
email | string | Required, valid email |
password | string | Required, non-empty |
{
"success": true,
"message": "Login successful",
"data": {
"mfaRequired": false,
"accessToken": "eyJhbGciOiJIUzI1NiIs..."
}
}{
"success": true,
"message": "MFA verification required",
"data": {
"mfaRequired": true,
"tempToken": "eyJhbGciOiJIUzI1NiIs..."
}
}On success without MFA, a refreshToken cookie is set automatically (httpOnly, secure, sameSite: strict, 30-day expiry).
10 requests per 15 minutes per IP.
/auth/meAuth Required{
"success": true,
"data": {
"id": "cm...",
"fullname": "John Doe",
"email": "john@example.com",
"isVerified": true,
"mfaEnabled": false,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
}
}/auth/verify-email| Field | Type | Rules |
|---|---|---|
token | string | Required, the token from the email link |
{
"success": true,
"message": "Email verified successfully"
}5 requests per 10 minutes per IP.
/auth/forgot-password| Field | Type | Rules |
|---|---|---|
email | string | Required, valid email |
{
"success": true,
"message": "If an account exists, a reset link has been sent."
}3 requests per 15 minutes per IP.
/auth/reset-password| Field | Type | Rules |
|---|---|---|
token | string | Required, the token from the email link |
newPassword | string | 8–128 chars, 1 uppercase, 1 lowercase, 1 digit, 1 special char |
{
"success": true,
"message": "Password reset successful"
}5 requests per 15 minutes per IP.
/auth/change-passwordAuth Required| Field | Type | Rules |
|---|---|---|
currentPassword | string | Required, current password |
newPassword | string | 8–128 chars, 1 uppercase, 1 lowercase, 1 digit, 1 special char |
{
"success": true,
"message": "Password changed successfully"
}3 requests per 15 minutes per IP.
/auth/refresh-tokenNo request body needed. The refreshToken cookie is read automatically.
{
"success": true,
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIs..."
}
}Each refresh consumes the old refresh token and issues a new one. If the same refresh token is used twice, all sessions for that user are revoked (possible token theft detected).
10 requests per 15 minutes per IP.
/auth/logoutAuth Required{
"success": true,
"message": "Logged out successfully"
}/auth/logout-allAuth Required{
"success": true,
"message": "All sessions revoked"
}OAuth Endpoints#
/auth/oauth/:providergoogle, github, facebook.# Opens Google's OAuth consent screen
GET http://localhost:5000/auth/oauth/google/auth/oauth/callback/:providerThis endpoint is called by the OAuth provider, not directly by your frontend. It redirects to {FRONTEND_URL}/auth/callback?code={oneTimeCode}.
/auth/oauth/exchange| Field | Type | Rules |
|---|---|---|
code | string | Required, one-time code from the OAuth redirect |
{
"success": true,
"message": "OAuth login successful",
"data": {
"mfaRequired": false,
"accessToken": "eyJhbGciOiJIUzI1NiIs..."
}
}{
"success": true,
"message": "MFA verification required",
"data": {
"mfaRequired": true,
"tempToken": "eyJhbGciOiJIUzI1NiIs..."
}
}MFA Endpoints#
/auth/mfa/setupAuth Required{
"success": true,
"data": {
"secret": "JBSWY3DPEHPK3PXP",
"uri": "otpauth://totp/AuthHero:john@example.com?secret=JBSWY3DPEHPK3PXP&issuer=AuthHero",
"backupCodes": [
"a1b2c3d4",
"e5f6a7b8",
"c9d0e1f2",
"a3b4c5d6",
"e7f8a9b0",
"c1d2e3f4",
"a5b6c7d8",
"e9f0a1b2"
]
}
}The backup codes are only returned in plaintext here and during regeneration. After the user confirms setup, they are stored as argon2 hashes and cannot be retrieved. Display them clearly and tell the user to save them.
/auth/mfa/verifyAuth Required| Field | Type | Rules |
|---|---|---|
token | string | Exactly 6 digits from authenticator app |
{
"success": true,
"message": "MFA enabled successfully"
}/auth/mfa/challenge| Field | Type | Rules |
|---|---|---|
tempToken | string | Required, the MFA temp token from login response |
code | string | 6-digit TOTP code or 8-char backup code |
{
"success": true,
"message": "MFA challenge passed",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIs..."
}
}Each backup code can only be used once. After use, it is permanently deleted from the database.
5 requests per 5 minutes per IP. TOTP codes are only 6 digits, so rate limiting is critical to prevent brute-force attacks.
/auth/mfa/disableAuth Required| Field | Type | Rules |
|---|---|---|
code | string | 6-digit TOTP code or 8-char backup code (6–8 chars) |
{
"success": true,
"message": "MFA disabled successfully"
}/auth/mfa/regenerate-backup-codesAuth Required| Field | Type | Rules |
|---|---|---|
code | string | Exactly 6 digits from authenticator app |
{
"success": true,
"data": {
"backupCodes": [
"f1e2d3c4",
"b5a6f7e8",
"d9c0b1a2",
"f3e4d5c6",
"b7a8f9e0",
"d1c2b3a4",
"f5e6d7c8",
"b9a0f1e2"
]
}
}Authentication#
Endpoints marked with Requires Auth expect an Authorization header with a Bearer token:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...The access token is a signed JWT with a 15-minute lifetime. When it expires, use POST /auth/refresh-token to get a new one.
The authentication middleware validates the JWT signature, checks that the session still exists in the database, and attaches req.user with userId and sessionId.
Password Policy#
All password fields (registration, reset, change) use the same validation rules:
| Rule | Requirement |
|---|---|
Minimum length | 8 characters |
Maximum length | 128 characters |
Uppercase | At least 1 uppercase letter (A-Z) |
Lowercase | At least 1 lowercase letter (a-z) |
Digit | At least 1 number (0-9) |
Special character | At least 1 non-alphanumeric character |
Rate Limits#
All rate limits are per-IP and backed by Redis for distributed deployments:
| Endpoint | Max Requests | Window |
|---|---|---|
POST /auth/register | 5 | 10 minutes |
POST /auth/login | 10 | 15 minutes |
POST /auth/verify-email | 5 | 10 minutes |
POST /auth/forgot-password | 3 | 15 minutes |
POST /auth/reset-password | 5 | 15 minutes |
POST /auth/refresh-token | 10 | 15 minutes |
POST /auth/change-password | 3 | 15 minutes |
POST /auth/mfa/challenge | 5 | 5 minutes |
When a rate limit is exceeded, the server returns:
{
"success": false,
"message": "Too many requests",
"retryAfter": 540
}