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_tokenstable. - Expiration:
REFRESH_TOKEN_EXPIRE(days). - Rotated on each refresh — old token is deleted in the same transaction as the new pair.
- Type field distinguishes
uservsadmintokens.
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
Authorizationheader. - Sets
request.userIdfrom 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: truefor cookie support if needed.
Client integration checklist
- Request OTP with normalized Iranian mobile number.
- Verify OTP and store both
accessToken.tokenandrefreshToken.token. - Send
Authorization: Bearer <accessToken>on protected requests. - On 401, call the refresh endpoint with the refresh token.
- Replace stored tokens with the new pair from the refresh response.