Files
2026-07-02 15:59:46 +03:30

6.1 KiB

Authentication & Authorization

Negareh API supports two actor types — users (customers) and admins (back-office). Both authenticate via phone OTP and receive JWT access tokens with refresh tokens.

Overview

┌─────────────┐     OTP request      ┌─────────────┐
│   Client    │ ──────────────────►  │  Auth API   │
│             │ ◄──────────────────  │  (SMS/Redis)│
└─────────────┘     OTP code         └─────────────┘
       │                                    │
       │         OTP verify                 │
       └──────────────────────────────────► │
                                            ▼
                              ┌─────────────────────────┐
                              │ Access JWT + Refresh    │
                              │ token (hashed in DB)    │
                              └─────────────────────────┘

OTP flow

User (public)

Endpoint Method Description
/public/auth/otp/request POST Send OTP to phone (creates user on verify)
/public/auth/otp/verify POST Verify OTP → returns tokens + user profile
/public/auth/refresh POST Exchange refresh token for new token pair

Admin

Endpoint Method Description
/admin/auth/otp/request POST Send OTP (phone must exist in admins table)
/admin/auth/otp/verify POST Verify OTP → returns tokens + admin profile
/admin/auth/refresh POST Exchange refresh token for new token pair

OTP details

  • Phone numbers are normalized to local format via normalizePhoneToLocal() (09xxxxxxxxx).
  • OTP codes are 5-digit numeric values stored in Redis with TTL (OTP_EXPIRATION_TIME, default 240 seconds).
  • OTP request endpoints are rate-limited (@Throttle: 3 requests per 180 seconds).
  • SMS delivery uses SMS.ir patterns (configured via SMS_* env vars).

Request / verify DTOs

// Request OTP
{ "phone": "09362532122" }

// Verify OTP
{ "phone": "09362532122", "otp": "12345" }

JWT tokens

Access token

  • Signed with JWT_SECRET.
  • Expiration: JWT_EXPIRATION_TIME (seconds).
  • Sent as Authorization: Bearer <token>.

User payload:

{ userId: string }

Admin payload:

{ adminId: string }

Refresh token

  • Opaque 64-character hex string (not a JWT).
  • Stored hashed (SHA-256) in the refresh_tokens table.
  • Expiration: REFRESH_TOKEN_EXPIRE (days).
  • Rotated on each refresh — old token is deleted in the same transaction as the new pair.
  • Type field distinguishes user vs admin tokens.

Refresh response shape

{
  "accessToken": { "token": "...", "expire": 1710000000000 },
  "refreshToken": { "token": "...", "expire": 1711000000000 }
}

expire values are Unix timestamps in milliseconds.

Guards

AuthGuard (users)

  • Applied with @UseGuards(AuthGuard) on public routes that require a logged-in user.
  • Verifies JWT from Authorization header.
  • Sets request.userId from the token payload.
  • Extract identity in controllers with @UserId().

AdminAuthGuard (admins)

  • Applied with @UseGuards(AdminAuthGuard) on admin routes.
  • Verifies JWT and sets request.adminId.
  • Extract identity with @AdminId().
  • Reads @Permissions(...) metadata from the handler/class for RBAC checks.

Role-based access control (RBAC)

Admins have a role with attached permissions. Permission names are defined in PermissionEnum (e.g. view_orders, create_invoice, manage_admins).

Declaring required permissions

@Get('admin/orders')
@UseGuards(AdminAuthGuard)
@Permissions(PermissionEnum.VIEW_ORDERS)
findAllAsAdmin(@Query() dto: FindOrdersDto) {
  return this.orderService.findOrdersAsAdmin(dto);
}

The @Permissions() decorator stores required permission keys in metadata. AdminAuthGuard reads them via Reflector.

Note: Permission enforcement against the database/cache is partially implemented. The guard structure and decorators are in place; ensure permission checks are active before relying on them in production.

Route prefixes

Routes are namespaced by audience in the controller path (not a global prefix):

Prefix Audience Guard
public/ End users AuthGuard (when protected)
admin/ Back-office AdminAuthGuard

Unauthenticated routes use public/auth/... or admin/auth/... without guards.

WebSocket auth

Admin WebSocket connections use WsAdminAuthGuard in the notifications module. Follow the same JWT verification pattern as HTTP admin auth.

Environment variables

Variable Purpose
JWT_SECRET Signing key for access tokens
JWT_EXPIRATION_TIME Access token TTL (seconds)
REFRESH_TOKEN_EXPIRE Refresh token TTL (days)
OTP_EXPIRATION_TIME OTP cache TTL (seconds)
REDIS_URI / REDIS_HOST OTP and cache storage
SMS_BASE_URL, SMS_API_KEY, SMS_PATTERN_OTP SMS delivery

Security practices

  • Refresh tokens are hashed at rest; raw tokens are only returned once to the client.
  • Token refresh runs inside a database transaction to prevent race conditions.
  • Rate limiting on OTP and refresh endpoints reduces brute-force risk.
  • Never log tokens, OTP codes, or secrets.
  • Use HTTPS in production; CORS is enabled with credentials: true for cookie support if needed.

Client integration checklist

  1. Request OTP with normalized Iranian mobile number.
  2. Verify OTP and store both accessToken.token and refreshToken.token.
  3. Send Authorization: Bearer <accessToken> on protected requests.
  4. On 401, call the refresh endpoint with the refresh token.
  5. Replace stored tokens with the new pair from the refresh response.