احراز هویت (Authentication)¶
مقدمه¶
تمامی درخواستها به API (به جز اندپوینتهای login و refresh) نیاز به احراز هویت دارند. این سیستم بر اساس JWT (JSON Web Token) پیادهسازی شده است.
انواع توکنها¶
1. Access Token¶
-
مدت زمان: ۱۰ دقیقه
-
کاربرد: دسترسی به API
-
فرمت: JWT با امضای نامتقارن
-
نحوه استفاده: در هدر Authorization
2. Refresh Token¶
-
مدت زمان: ۷ روز
-
کاربرد: دریافت Access Token جدید
-
فرمت: رشته منحصر به فرد
-
نحوه استفاده: فقط برای refresh
گام 1: دریافت توکن اولیه¶
اندپوینت Login¶
curl -X POST "$BASE_URL/api/sso/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{
"client_id": "your_client_id",
"client_secret": "your_client_secret"
}'
توضیحات:
- client_id: شناسه کلاینت بانک که از مرکز مبادله دریافت کردهاست
- client_secret: رمز عبور مربوط به کلاینت
نمونه موفق¶
درخواست:
curl -X POST "$BASE_URL/api/sso/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{
"client_id": "client_abc123",
"client_secret": "secret_xyz789"
}'
پاسخ (200 OK):
{
"iss": "{BASE_URL}",
"sub": "0197f464-46e0-7392-a54f-b08f07be6926",
"iat": 1767120162,
"exp": 1767120762,
"jti": "a0b3af36-e498-4a62-9499-5aea9d7be5fe",
...
"tokens": {
"access": "<access_token>",
"refresh": "<refresh_token>"
}
}
نمونه ناموفق 1: کلاینت نامعتبر¶
درخواست:
curl -X POST "$BASE_URL/api/sso/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{
"client_id": "wrong_client",
"client_secret": "wrong_secret"
}'
پاسخ (401 Unauthorized):
{
"message": {
"en": "Authentication failed"
},
"error": "invalid_credentials",
"detail": "Authentication failed"
}
نمونه ناموفق 2: دادههای ناقص¶
درخواست:
curl -X POST "$BASE_URL/api/sso/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{
"client_id": "client_abc123"
# client_secret حذف شده
}'
پاسخ (422 Unprocessable Entity):
{
"detail": [
{
"type": "missing",
"loc": [
"body",
"client_secret"
],
"msg": "Field required",
"input": {
"client_id": "client_abc123"
}
}
]
}
نمونه ناموفق 3: فرمت JSON نامعتبر¶
درخواست:
curl -X POST "$BASE_URL/api/sso/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{
"client_id": "client_abc123",
"client_secret": "secret_xyz789",
}' # فرمت صحیح است، اما فرض کنیم کاما اضافی دارد
پاسخ (422 Bad Request):
{
"detail": [
{
"type": "json_invalid",
"loc": [
"body",
66
],
"msg": "JSON decode error",
"input": {},
"ctx": {
"error": "Illegal trailing comma before end of object"
}
}
]
}
گام 2: استفاده از Access Token¶
فرمت هدر¶
در تمامی درخواستها به API، هدر زیر الزامی است:
نمونه استفاده¶
درخواست موفق:
# تعریف متغیرها
JWT_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
# درخواست به API
curl -X GET "$BASE_URL/api/exchange/v1/symbols" \
-H "Authorization: Bearer $JWT_TOKEN"
پاسخ (200 OK):
{
"heads": {
"id": {
"en": "Id"
},
"revision_id": {
"en": "Revision Id"
},
"uid": {
"en": "Uid"
},
"created_at": {
"en": "Created At"
},
"updated_at": {
"en": "Updated At"
},
"is_deleted": {
"en": "Is Deleted"
},
"meta_data": {
"en": "Meta Data"
},
"user_id": {
"en": "User Id"
},
"symbol": {
"en": "Symbol"
},
"price_precision": {
"en": "Price Precision"
},
"quantity_precision": {
"en": "Quantity Precision"
},
"min_order_quantity": {
"en": "Min Order Quantity"
},
"tick_size": {
"en": "Tick Size"
},
"daily_fluctuation_price_limit_percent": {
"en": "Daily Fluctuation Price Limit Percent"
},
"description": {
"en": "Description"
},
"is_active": {
"en": "Is Active"
}
},
"items": [
{
"uid": "01995196-ef5e-7239-87b9-e93d61004317",
"created_at": "2025-09-16T08:14:53.306000",
"updated_at": "2025-09-16T08:14:53.306000",
"is_deleted": false,
"meta_data": null,
"user_id": "admin",
"symbol": "EUR-IRR",
"price_precision": 0,
"quantity_precision": -2,
"min_order_quantity": "100",
"tick_size": "100",
"daily_fluctuation_price_limit_percent": "5",
"description": "Euro to Iranian Rial",
"is_active": true,
"properties": {
"EUR": {
"name": {
"fa": "یورو",
"en": "Euro"
},
"symbol": "EUR",
"precision": 2,
"icon": "https://flagcdn.com/w40/eu.png",
"is_crypto": false,
"color": "#26a17b",
"unit_value_irr": 1000000
},
"IRR": {
"name": {
"fa": "ریال",
"en": "Iranian Rial"
},
"symbol": "IRR",
"precision": 0,
"icon": "https://flagcdn.com/w40/ir.png",
"is_crypto": false,
"color": "#1976d2",
"unit_value_irr": 1
}
}
},
{
"uid": "019855a6-0f44-7767-9bfb-65672b207fde",
"created_at": "2025-07-29T10:06:43.012000",
"updated_at": "2025-07-29T10:06:43.012000",
"is_deleted": false,
"meta_data": null,
"user_id": "admin",
"symbol": "USD-IRR",
"price_precision": 0,
"quantity_precision": -2,
"min_order_quantity": "100",
"tick_size": "100",
"daily_fluctuation_price_limit_percent": "5",
"description": "US Dollar to Iranian Rial",
"is_active": true,
"properties": {
"USD": {
"name": {
"fa": "دلار",
"en": "Dollar"
},
"symbol": "USD",
"precision": 2,
"icon": "https://flagcdn.com/w40/us.png",
"is_crypto": false,
"color": "#f7931a",
"unit_value_irr": 900000
},
"IRR": {
"name": {
"fa": "ریال",
"en": "Iranian Rial"
},
"symbol": "IRR",
"precision": 0,
"icon": "https://flagcdn.com/w40/ir.png",
"is_crypto": false,
"color": "#1976d2",
"unit_value_irr": 1
}
}
}
],
"total": 2,
"offset": 0,
"limit": 10
}```
### نمونه ناموفق 1: بدون توکن
**درخواست:**
```bash
curl -X GET "$BASE_URL/api/exchange/v1/symbols"
پاسخ (401 Unauthorized):
نمونه ناموفق 2: توکن منقضی شده¶
درخواست:
curl -X GET "$BASE_URL/api/exchange/v1/symbols" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.expired_token"
پاسخ (401 Unauthorized):
نمونه ناموفق 3: فرمت اشتباه - بدون Bearer¶
درخواست:
curl -X GET "$BASE_URL/api/exchange/v1/symbols" \
-H "Authorization: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
پاسخ (401 Unauthorized):
{
"message": "JWT has an invalid format",
"error": "Unauthorized",
"detail": "JWT has an invalid format"
}
نمونه ناموفق 4: توکن دستکاری شده¶
درخواست:
curl -X GET "$BASE_URL/api/exchange/v1/symbols" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.modified_part"
پاسخ (401 Unauthorized):
{
"message": "JWT signature is invalid",
"error": "Unauthorized",
"detail": "JWT signature is invalid"
}
نمونه ناموفق 5: توکن بدون سطح دسترسی مناسب¶
درخواست:
# کلاینت A سعی میکند از توکن کلاینت B استفاده کند
curl -X GET "$BASE_URL/api/sso/v1/users/user_123" \
-H "Authorization: Bearer <token_of_client_B>"
پاسخ (403 Forbidden):
گام 3: رفرش توکن¶
اندپوینت Refresh¶
پارامترهای ورودی¶
نمونه موفق¶
درخواست:
curl -X POST ${BASE_URL}/api/sso/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "refresh_token_xyz789abc123"
}'
پاسخ (200 OK):
{
"status": "refreshed",
"tokens": {
"access": "<access_token>",
"refresh": "<refresh_token>"
},
"expires_in": 600
}
نمونه ناموفق 1: Refresh Token نامعتبر¶
درخواست:
curl -X POST "${BASE_URL}/api/auth/v1/refresh" \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "invalid_refresh_token"
}'
پاسخ (401 Unauthorized):
{
"message": "JWT has an invalid format",
"error": "Unauthorized",
"detail": "JWT has an invalid format"
}
نمونه ناموفق 2: داده ناقص¶
درخواست:
پاسخ (422 Unprocessable Entity):
{
"detail": [
{
"type": "missing",
"loc": [
"body",
"refresh_token"
],
"msg": "Field required",
"input": {}
}
]
}
ساختار JWT Token¶
Payload نمونه¶
{
"iss": "$BASE_URL",
"sub": "0197f464-46e0-7392-a54f-b08f07be6926",
"iat": 1767121796,
"exp": 1767122396,
"jti": "322fa880-b4c6-4a05-9db9-f952edc1b5f0",
"token_type": "access",
"session_id": "019b70aa-bceb-7f9c-a820-ceeafea93310",
"tenant_id": "0197f464-46c8-72a1-8b23-0819b8622a0c",
"roles": [
"admin",
"user"
],
"scopes": [
"*:*"
],
"amr": [
"client_secret"
]
}
توضیحات فیلدها¶
-
iss: صادرکننده توکن (issuer) -
sub: شناسه کلاینت (subject) -
iat: زمان صدور (issued at time - Unix timestamp) -
exp: زمان انقضا (expiration time - Unix timestamp) -
jti: شناسه منحصر به فرد توکن (JWT ID) -
token_type: نوع توکن (access/refresh) -
session_id: شناسه جلسه -
tenant_id: شناسه tenant -
roles: نقشهای کاربر (لیست رشتهها) -
scopes: حوزههای دسترسی (لیست رشتهها) -
amr: روشهای احراز هویت (authentication methods)
چرخه عمر توکن¶
sequenceDiagram
participant Client
participant AuthAPI
participant OtherAPI
Client->>AuthAPI: POST /login (client_id, secret)
AuthAPI-->>Client: access_token + refresh_token
Client->>OtherAPI: GET /resource (با access_token)
OtherAPI-->>Client: دادهها
Note over Client,AuthAPI: access_token منقضی میشود
Client->>AuthAPI: POST /refresh (refresh_token)
AuthAPI-->>Client: access_token جدید
Client->>OtherAPI: GET /resource (با access_token جدید)
OtherAPI-->>Client: دادهها
Note over Client,AuthAPI: refresh_token منقضی میشود
Client->>AuthAPI: POST /login (جدد)
AuthAPI-->>Client: access_token + refresh_token جدید
چکلیست امنیتی¶
✅ درست: - [ ] توکن را در متغیر محیطی ذخیره کنید
-
[ ] از HTTPS استفاده کنید
-
[ ] توکن را در لاگها نمایش ندهید
-
[ ] توکن را در URL ندهید
-
[ ] هر ۲۴ ساعت رفرش کنید
-
[ ] refresh_token را امن نگه دارید
❌ اشتباه: - [ ] توکن را در کد source control قرار دهید
-
[ ] از HTTP استفاده کنید
-
[ ] توکن را در لاگها بنویسید
-
[ ] توکن منقضی شده را دوباره استفاده کنید
-
[ ] refresh_token را به اشتراک بگذارید
خلاصه¶
- Login: دریافت access_token و refresh_token با client_id/secret
- استفاده: ارسال
Authorization: Bearer <access_token>در هر درخواست - Refresh: وقتی access_token منقضی شد، با refresh_token جدید بگیرید
- امنیت: همیشه از HTTPS استفاده کنید و توکنها را محرمانه نگه دارید
بعدی: مطالعه قراردادها و کانوانشنها