Happy House - Ecommerce Docs
Developer Resourcesauth

Auth Module API & Integration Guide

Endpoint reference for admin auth and customer mobile auth, including the Google and Facebook OAuth customer flows, provider linking, and edge-case behavior.

Auth - API & Integration Guide

1. Route Surfaces

Auth is split across two surfaces:

  • Admin / backoffice auth: /api/auth/*
  • Customer mobile auth: /api/mobile/auth/*

OAuth is customer-only and is exposed only on /api/mobile/auth/*. Two providers are supported, Google and Facebook, served by one implementation. There is no OAuth endpoint under /api/auth/*.


2. Endpoint Reference

2.1 Admin auth (/api/auth/*)

MethodPathAuthDescription
POST/api/auth/login/emailPublicAdmin email/password login
POST/api/auth/password/forgotPublicRequest password reset
POST/api/auth/password/resetPublicReset password via token or OTP
POST/api/auth/password/setJWTSet or change password for authenticated user
POST/api/auth/email/verify/resendPublicResend email verification
POST/api/auth/email/verify/confirmPublicConfirm email verification
POST/api/auth/refreshPublicRotate refresh/access tokens
GET/api/auth/meJWTGet current user profile (includes hasPassword)
GET/api/auth/permissionsJWTGet current user permissions
POST/api/auth/phone/verify/requestJWTSend phone verification OTP
POST/api/auth/phone/verify/confirmJWTConfirm phone verification OTP
DELETE/api/auth/logoutJWTLogout current session
POST/api/auth/account-deletion/requestJWTRequest account deletion (7-day grace period)
DELETE/api/auth/account-deletion/cancelJWTCancel a pending account deletion before the 7-day window elapses
GET/api/auth/account-deletion/statusJWTGet current deletion/recovery status
POST/api/auth/account-deletion/recoverPublicRequest account recovery OTP for a pending-deletion account
POST/api/auth/account-deletion/recover/confirmPublicConfirm account recovery via OTP

2.2 Customer mobile auth (/api/mobile/auth/*)

MethodPathAuthDescription
POST/api/mobile/auth/registerPublicCustomer register (email/password)
POST/api/mobile/auth/login/emailPublicCustomer email/password login
GET/api/mobile/auth/googlePublicStart Google OAuth redirect (redirect_uri required)
GET/api/mobile/auth/google/callbackPublicGoogle callback; redirects to frontend with one-time code
POST/api/mobile/auth/google/exchangePublicExchange one-time code for user + tokens
GET/api/mobile/auth/facebookPublicStart Facebook OAuth redirect (redirect_uri required)
GET/api/mobile/auth/facebook/callbackPublicFacebook callback; redirects to frontend with one-time code
POST/api/mobile/auth/facebook/exchangePublicExchange one-time code for user + tokens
POST/api/mobile/auth/facebook/linkJWTStart attaching Facebook to the signed-in account
DELETE/api/mobile/auth/facebook/linkJWTDetach Facebook from the signed-in account
POST/api/mobile/auth/refreshPublicRotate refresh/access tokens
POST/api/mobile/auth/password/forgotPublicRequest password reset
POST/api/mobile/auth/password/resetPublicReset password via token or OTP
POST/api/mobile/auth/password/setJWTSet password for authenticated mobile user
POST/api/mobile/auth/email/verify/resendPublicResend email verification
POST/api/mobile/auth/email/verify/confirmPublicConfirm email verification
GET/api/mobile/auth/meJWTGet current user profile (includes hasPassword)
DELETE/api/mobile/auth/logoutJWTLogout current session
POST/api/mobile/auth/account-deletion/requestJWTRequest account deletion (7-day grace period)
DELETE/api/mobile/auth/account-deletion/cancelJWTCancel a pending account deletion before the 7-day window elapses
GET/api/mobile/auth/account-deletion/statusJWTGet current deletion/recovery status
POST/api/mobile/auth/account-deletion/recoverPublicRequest account recovery OTP for a pending-deletion account
POST/api/mobile/auth/account-deletion/recover/confirmPublicConfirm account recovery via OTP

3. Key Contracts

3.1 Customer email login

POST /api/mobile/auth/login/email

{
  "email": "customer@example.com",
  "password": "StrongPass123",
  "deviceInfo": {
    "deviceId": "android-abc",
    "deviceType": "android",
    "deviceName": "Pixel"
  }
}

3.2 Google one-time code exchange success shape (customer)

POST /api/mobile/auth/google/exchange

{
  "code": "e2f6d17ea01b5b824db6f7d8f936f1b5e97cef25afd8515b"
}

Response:

{
  "message": "Google login successful.",
  "data": {
    "user": {
      "id": "uuidv7",
      "name": "Tenzin Sherpa",
      "email": "tenzin@example.com",
      "image": "https://lh3.googleusercontent.com/...",
      "emailVerified": true,
      "phone": null,
      "phoneVerified": false,
      "role": "customer"
    },
    "tokens": {
      "sessionId": "uuidv7",
      "accessToken": "<jwt>",
      "refreshToken": "<opaque-token>"
    }
  }
}

3.3 Customer registration duplicate-email response

POST /api/mobile/auth/register

If the submitted email already belongs to an existing customer account, registration fails with one of three distinct 409 Conflict responses, each carrying a stable errorCode:

errorCodeMeaningFrontend handling
ACCOUNT_PENDING_DELETIONThe account is scheduled for deletionDirect the user to the account-recovery flow before they can reuse this email
AUTH_EMAIL_VERIFICATION_PENDINGAn account with this email exists but has never been verifiedOffer "Resend verification email" — call POST /api/mobile/auth/email/verify/resend
AUTH_EMAIL_ALREADY_EXISTSAn account with this email exists and is already verifiedOffer "Log in instead" — do not attempt another registration

Example response body (AUTH_EMAIL_VERIFICATION_PENDING):

{
  "statusCode": 409,
  "errorCode": "AUTH_EMAIL_VERIFICATION_PENDING",
  "message": "An account with this email already exists but has not been verified yet. Please verify your email or request a new verification email."
}

Registration never auto-resends the verification email on a duplicate attempt — the frontend must call the resend endpoint explicitly if it wants to offer that action.


4. Set Password

Authenticated endpoints for setting or changing a user's password. Google-only users use this to create an EMAIL account with a password. Existing EMAIL users use this to change their password.

4.1 POST /api/auth/password/set

4.2 POST /api/mobile/auth/password/set

Set or change password for an authenticated user.

Authentication: JWT Bearer token

Rate limit: 5 requests per 15 minutes

Request body:

{
  "newPassword": "NewPass@1234",
  "currentPassword": "CurrentPass@123"
}

currentPassword is required only if the user already has a password.

Response (200 OK):

{
  "message": "Password set successfully.",
  "data": null,
  "errorCode": null
}

Errors:

  • 400 Bad RequestcurrentPassword missing when user already has password
  • 401 Unauthorized — Invalid JWT or incorrect currentPassword
  • 429 Too Many Requests — Rate limit exceeded

4.5 Account Deletion & Recovery

Request deletion

Authentication: JWT Bearer token

Rate limit: 2 requests per 24 hours

Response (200 OK):

{
  "message": "Account deletion requested. You will be logged out.",
  "data": {
    "success": true,
    "deletionScheduledAt": "2026-07-21T10:00:00.000Z"
  }
}

Cancel deletion

Authentication: JWT Bearer token. Must be called before the 7-day deletion window elapses — returns a conflict error once the window has passed.

Rate limit: 5 requests per 24 hours

Deletion status

Authentication: JWT Bearer token. Returns status, deletionScheduledAt, deleteBlockUntil.

Recover account (request OTP)

Authentication: Public (email only — used when the account is banned/inaccessible during the pending-deletion window)

Rate limit: 3 requests per hour

Request body:

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

Response (200 OK):

{
  "message": "If the email exists and the account is in pending-deletion status, a recovery code has been sent.",
  "data": {
    "success": true
  }
}

Confirm account recovery

Authentication: Public

Rate limit: 5 requests per 15 minutes

Request body:

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

Recovering resets the account to active and applies a 48-hour cooldown (deleteBlockUntil) before another deletion request can be made.


5. hasPassword Field

All profile responses from:

  • GET /api/auth/me
  • GET /api/mobile/auth/me

include hasPassword: boolean.

  • hasPassword: false means no EMAIL provider password exists yet.
  • hasPassword: true means EMAIL provider password is set.

Frontend integration:

  • hasPassword === false → show "Add Password"
  • hasPassword === true → show "Change Password"

6. OAuth Behavior Matrix (Actual Runtime)

Both providers are resolved by AuthLoginService.handleOAuthLogin().

6.1 The identity rule

The provider's subject id is the identity. The email is a display attribute and is never a join key. Resolving a login by email is an account takeover for any provider that does not assert the address is verified: an attacker sets their provider profile email to the victim's and matches.

Resolution runs in this order, and stops at the first match:

#ConditionResult
1Profile carries no email400 AUTH_{PROVIDER}_EMAIL_REQUIRED
2An account row exists for (provider_id, account_id)That row's customer is the identity. Ban and deletion gates run, then the session is issued. account_id is never rewritten.
3The email belongs to an admin401 AUTH_{PROVIDER}_LOGIN_NOT_ALLOWED_FOR_ADMIN
4The email belongs to a live customerProvider-dependent — see 6.2
5The email belongs to a customer being deleted409 AUTH_OAUTH_ACCOUNT_RECOVERY_REQUIRED, or AUTH_OAUTH_EMAIL_UNAVAILABLE if already deleted
6Nobody holds the emailCustomer + link created in one transaction

6.2 Where the providers differ

Exactly one branch, row 4, and it turns on two things: whether the provider is capable of asserting a verified email, and whether it did for this profile.

ProviderCapableRow 4 behaviour
GoogleyesAdopts the existing customer and marks emailVerified = true — but only when the profile carried email_verified: true. Without that claim it behaves like Facebook and returns AUTH_GOOGLE_ACCOUNT_LINK_REQUIRED.
FacebooknoAlways 409 AUTH_FACEBOOK_ACCOUNT_LINK_REQUIRED. Facebook's profile has no verification signal at all, so it may never adopt an account on an email match.

A new customer created by Facebook has emailVerified = false; one created by a verified Google profile has emailVerified = true.

When a verifying provider adopts a customer row whose email was not previously proved, every link belonging to a non-asserting provider (currently Facebook) is deleted in the same transaction.

This closes an otherwise real takeover: an attacker signs up with Facebook using the victim's address, creating an unproved row; the victim later signs in with Google, which proves the address and adopts the row. Without the revocation the attacker keeps Facebook access to it. The real owner re-links Facebook deliberately via POST /api/mobile/auth/facebook/link.

6.4 Browser binding

Every OAuth start sets __Host-hs_oauth_nonce (HttpOnly, Secure, SameSite=Lax, Path=/), and the callback rejects a state whose nonce does not match with AUTH_OAUTH_STATE_NOT_BOUND_TO_BROWSER.

SameSite=Lax is required, not a preference — the callback arrives as a cross-site top-level navigation from the provider's domain, and Strict would withhold the cookie on exactly that request, silently failing every login.

Without this the signed state binds only to the redirect URI, which anyone can satisfy because the start route is public — a login-CSRF that lands a victim inside the attacker's account.

6.5 Provider failures redirect, they do not return JSON

Guards run before route handlers, so a provider-side failure (a cancelled consent dialog) never reached the controller's error handling. It now resolves to a redirect back to the allowlisted redirect_uri.

The error values the anonymous redirect may carry are deliberately coarse — oauth_failed, email_required, access_denied. A more precise value would let anyone probe which addresses are registered.

6.6 Provider linking

POST /api/mobile/auth/facebook/link returns an authorizationUrl. The authenticated customer id is sealed into the signed state, never taken from the request, so a client cannot ask to link a provider onto another account. DELETE refuses with AUTH_LAST_CREDENTIAL_CANNOT_BE_REMOVED when the link is the only way the account can sign in.

Important note for pending email-verification users

A customer who registered by email/password without verifying, then signs in with a verified Google profile on the same address, is marked verified and issued tokens. A Facebook sign-in on that address is refused with AUTH_FACEBOOK_ACCOUNT_LINK_REQUIRED instead.


7. Operational Notes

Google — GOOGLE_OAUTH_REDIRECT_ALLOWLIST and GOOGLE_OAUTH_STATE_SECRET are required at boot; the app will not start without them.

  • GOOGLE_CALLBACK_URL
  • GOOGLE_OAUTH_REDIRECT_ALLOWLIST (required)
  • GOOGLE_OAUTH_STATE_SECRET (required)
  • GOOGLE_OAUTH_STATE_TTL_SECONDS (default 300)
  • GOOGLE_OAUTH_CODE_TTL_SECONDS (default 90)
  • GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET — managed at runtime from the admin panel

Facebook — all optional. Facebook is enabled only when the allowlist and the state secret are both set; otherwise its five routes return 503 AUTH_OAUTH_PROVIDER_NOT_ENABLED and nothing else is affected. This is deliberate: an unconfigured optional provider must never prevent the API from booting, which is what making these required would do.

  • FACEBOOK_CALLBACK_URL
  • FACEBOOK_OAUTH_REDIRECT_ALLOWLIST
  • FACEBOOK_OAUTH_STATE_SECRET
  • FACEBOOK_OAUTH_STATE_TTL_SECONDS (default 300)
  • FACEBOOK_OAUTH_CODE_TTL_SECONDS (default 90)
  • FACEBOOK_CLIENT_ID / FACEBOOK_CLIENT_SECRET — managed at runtime from the admin panel

Shared:

  • OAUTH_CODE_DELIVERYquery (default) or fragment. Controls whether the one-time code reaches the storefront as ?code= or #code=. A query string lands in browser history, nginx access logs and the Referer of every subresource on the callback page; a fragment never leaves the browser. Ship the backend on query, teach the storefront to read a fragment, then flip.

Callback URL must match mobile auth callback route:

  • Example: if PORT=5002, use http://localhost:5002/api/mobile/auth/google/callback

Redirect allowlist must include exact frontend callback URLs, for example:

  • https://app.word.navneetverma.com/auth/callback

8. Guardrail Summary

  • Superadmin/admin login is email/password only. Both OAuth providers refuse admin addresses.
  • Customer OAuth login/signup is mobile auth only.
  • There is no OAuth endpoint under /api/auth/google* or /api/auth/facebook*.
  • Bearer tokens are never returned in browser redirect URLs — only a single-use 192-bit code with a 90-second TTL, redeemed once over a normal POST.
  • Provider access and refresh tokens are not persisted. Nothing in the codebase read them, so storing them was a plaintext credential at rest with no consumer.
  • The identity is the provider subject id, never the email.