Skip to main content

Auth Module

User registration, login, OTP verification, token refresh, and logout – the complete authentication gateway.

JWT OTP Google Rate Limit


📖 Table of Contents


Base Route : /api/v1/auth

All endpoints are public (no authentication required) except /logout, which requires a valid access token.


Endpoints Overview

MethodEndpointAccessDescription
POST/registerPublicRegister a new user (sends OTP email)
POST/verify-otpPublicVerify OTP, activate account, issue tokens
POST/loginPublicLogin with email + password
POST/googlePublicGoogle OAuth login (idToken)
POST/forgot-passwordPublicRequest password reset email
POST/reset-passwordPublicReset password using token
GET/refreshPublic (cookie)Obtain new access token using refresh token cookie
GET/logoutPrivate (Bearer)Logout, blacklist refresh token, clear cookie

Rate limits: All endpoints except /logout are protected by authLimiter (10 requests per hour per IP). See Rate Limiting.


Endpoint Details

POST /register

Create a new user account. An OTP is sent to the provided email address. The user must verify the OTP before logging in.

Request body:

{
"firstname": "Test",
"lastname": "User",
"email": "test@example.com",
"password": "SecurePassword123!",
"acceptPrivacyPolicy": true
}

Response (201 Created):

{
"success": true,
"statusCode": 201,
"message": "Registration successful. OTP sent to your email.",
"data": {
"user": {
"_id": "64a7b...",
"email": "test@example.com",
"firstname": "Test",
"lastname": "User",
"role": "USER",
"isEmailVerified": false
}
},
"timestamp": "2026-05-22T10:30:00.000Z"
}

Validation rules: See Validation Rules below.

Note: The password is hashed with bcrypt (12 rounds) before storage. The plaintext password is never logged.

POST /verify-otp

Verify the OTP sent during registration. Activates the account and issues access + refresh tokens.

Request body:

{
"email": "test@example.com",
"otp": "123456"
}

Response (200 OK):

{
"success": true,
"statusCode": 200,
"message": "Email verified successfully",
"data": {
"user": {
"_id": "64a7b...",
"email": "test@example.com",
"firstname": "Test",
"lastname": "User",
"role": "USER",
"isEmailVerified": true
},
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
},
"timestamp": "2026-05-22T10:32:00.000Z"
}

Cookie automatically set (invisible to JavaScript):

refreshToken: <jwt>; HttpOnly; Secure; SameSite=Strict; Max-Age=604800

The OTP is stored in Redis with a 10‑minute TTL. After verification, it is immediately deleted (prevents replay attacks).

POST /login

Authenticate an existing user with email and password. Issues access + refresh tokens.

Request body:

{
"email": "test@example.com",
"password": "SecurePassword123!"
}

Response: Same as /verify-otp (200 OK with accessToken and cookie).

Error cases:

  • Account not verified403 Forbidden with "Please verify your email address before logging in."
  • Account banned (isActive: false)403 Forbidden with account deactivated message (e.g., "This account has been deactivated.")
  • Invalid credentials401 Unauthorized with "Invalid email or password"

POST /google

Authenticate using Google OAuth 2.0. Accepts a Google idToken obtained from the frontend Google Sign-In library. If the email does not exist, a new user is created (with authProvider: "GOOGLE" and no password). If it exists, the user is logged in.

Request body:

{
"idToken": "google-oauth-id-token-string"
}

Response: Same as /login (200 OK with accessToken and cookie).

Google OAuth users cannot use password‑based endpoints (login, forgot/reset password). Attempting to call those endpoints will return 400 Bad Request.


POST /forgot-password

Request a password reset token. The token is sent to the user's email (the API does not return it). Only works for local (authProvider: "LOCAL") users.

Request body:

{
"email": "test@example.com"
}

Response (200 OK):

{
"success": true,
"statusCode": 200,
"message": "If an account with that email exists, a password reset link has been sent.",
"data": null
}

For security, the response is the same even if the email does not exist (prevents email enumeration).


POST /reset-password

Reset the password using the token received via email.

Request body:

{
"email": "test@example.com",
"token": "reset-token-from-email",
"newPassword": "NewSecurePassword456!"
}

Response (200 OK):

{
"success": true,
"statusCode": 200,
"message": "Password reset successfully. You can now log in with your new password.",
"data": null
}

The token is invalidated after use (stored in Redis with TTL). Expired tokens return 400 Bad Request.

GET /refresh

Obtain a new access token using the refresh token stored in the HttpOnly cookie. No Authorization header required.

Request: No body, no headers (cookie is automatically sent).
Response (200 OK):

{
"success": true,
"statusCode": 200,
"message": "Access token refreshed",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
},
"timestamp": "2026-05-22T10:45:00.000Z"
}

Error (401 Unauthorized):

  • Missing or invalid refresh token cookie.
  • Refresh token expired (7 days).
  • Refresh token blacklisted (after logout).

Frontend implementation: Automatically call this endpoint when you receive a 401 on a protected request. See Authentication Guide.


GET /logout

Logout the current user. Requires a valid access token in the Authorization header.

Headers: Authorization: Bearer <access_token>

Response (200 OK):

{
"success": true,
"statusCode": 200,
"message": "Logged out successfully",
"data": null,
"timestamp": "2026-05-22T10:50:00.000Z"
}

What happens server‑side:

  • The refresh token cookie is cleared (overwritten with expired value).
  • The refresh token is added to a Redis blacklist (prevents reuse).
  • The access token is not blacklisted (short lifespan anyway).

Error (401 Unauthorized): If the access token is missing or invalid.


Validation Rules (Zod)

EndpointFieldRules
/registerfirstnamestring, 2‑50 chars, required
lastnamestring, 2‑50 chars, required
emailvalid email format, required
passwordmin 8 chars, at least one uppercase, one number, one special character, required
/verify-otpemailvalid email format, required
otpstring, exactly 6 digits, required
/loginemailvalid email format, required
passwordstring, min 1 char (actual validation is done by bcrypt compare), required
/googleidTokenstring, non‑empty, required
/forgot-passwordemailvalid email format, required
/reset-passwordemailvalid email format, required
tokenstring, non‑empty, required
newPasswordsame as password rule above, required

All schemas use .strict() – extra fields are rejected with 400 Bad Request.


Error Responses

StatusCodeExample messageWhen
400VALIDATION_FAILED"Validation Failed: email: Invalid email format"Zod validation fails
401UNAUTHORIZED"Invalid email or password"Wrong credentials
401UNAUTHORIZED"Invalid or expired refresh token"Refresh endpoint fails
403FORBIDDEN"Please verify your email address before logging in."Login before OTP verification
403FORBIDDEN"This account has been deactivated."User banned (isActive: false)
409CONFLICT"Email already registered. Please login instead."Duplicate email on register
429TOO_MANY_REQUESTS"Too many authentication attempts..."Rate limit exceeded (10/hour)

See Error Handling Guide for full details.


Runbook

For manual testing with Thunder Client / Postman, see ../../testing/auth-runbook.md. The runbook covers:

  • Registration → OTP verification → token issuance
  • Login → refresh → logout
  • Edge cases (invalid OTP, expired token, blacklist check)


Secure, stateless, and production‑proven – the Reshma‑Core authentication system.