957 lines
27 KiB
Markdown
957 lines
27 KiB
Markdown
|
# مستندات کامل سیستم OAuth 2.0 - Hesabix
|
|||
|
|
|||
|
## 📋 فهرست مطالب
|
|||
|
|
|||
|
1. [معرفی OAuth 2.0](#معرفی-oauth-20)
|
|||
|
2. [معماری سیستم](#معماری-سیستم)
|
|||
|
3. [بخش مدیریت](#بخش-مدیریت)
|
|||
|
4. [بخش کاربری](#بخش-کاربری)
|
|||
|
5. [API Documentation](#api-documentation)
|
|||
|
6. [نحوه اتصال](#نحوه-اتصال)
|
|||
|
7. [امنیت](#امنیت)
|
|||
|
8. [مثالهای عملی](#مثالهای-عملی)
|
|||
|
9. [عیبیابی](#عیبیابی)
|
|||
|
10. [پشتیبانی](#پشتیبانی)
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
## 🚀 معرفی OAuth 2.0
|
|||
|
|
|||
|
OAuth 2.0 یک پروتکل استاندارد برای احراز هویت و مجوزدهی است که به برنامههای خارجی اجازه میدهد بدون نیاز به رمز عبور، به حساب کاربران دسترسی داشته باشند.
|
|||
|
|
|||
|
### مزایای OAuth 2.0:
|
|||
|
- ✅ **امنیت بالا:** عدم اشتراکگذاری رمز عبور
|
|||
|
- ✅ **کنترل دسترسی:** محدود کردن دسترسیها با Scope
|
|||
|
- ✅ **قابلیت لغو:** امکان لغو دسترسی در هر زمان
|
|||
|
- ✅ **استاندارد:** سازگار با پروتکلهای جهانی
|
|||
|
- ✅ **IP Whitelist:** کنترل دسترسی بر اساس IP
|
|||
|
- ✅ **Rate Limiting:** محدودیت تعداد درخواست
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
## 🏗️ معماری سیستم
|
|||
|
|
|||
|
### اجزای اصلی:
|
|||
|
|
|||
|
```
|
|||
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|||
|
│ Client App │ │ OAuth Server │ │ Resource Owner │
|
|||
|
│ (Third Party) │◄──►│ (Hesabix) │◄──►│ (User) │
|
|||
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|||
|
```
|
|||
|
|
|||
|
### جریان OAuth:
|
|||
|
|
|||
|
1. **Client Registration:** ثبت برنامه در بخش مدیریت
|
|||
|
2. **Authorization Request:** درخواست مجوز از کاربر
|
|||
|
3. **User Consent:** تأیید کاربر
|
|||
|
4. **Authorization Code:** دریافت کد مجوز
|
|||
|
5. **Token Exchange:** تبدیل کد به Access Token
|
|||
|
6. **Resource Access:** دسترسی به منابع
|
|||
|
|
|||
|
### پایگاه داده:
|
|||
|
|
|||
|
```sql
|
|||
|
-- جداول OAuth
|
|||
|
oauth_application -- برنامههای ثبت شده
|
|||
|
oauth_scope -- محدودههای دسترسی
|
|||
|
oauth_authorization_code -- کدهای مجوز موقت
|
|||
|
oauth_access_token -- توکنهای دسترسی
|
|||
|
```
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
## ⚙️ بخش مدیریت
|
|||
|
|
|||
|
### 1. دسترسی به بخش مدیریت
|
|||
|
|
|||
|
```
|
|||
|
مدیریت سیستم → تنظیمات سیستم → تب "برنامههای OAuth"
|
|||
|
```
|
|||
|
|
|||
|
### 2. آمار کلی
|
|||
|
|
|||
|
سیستم چهار کارت آمار نمایش میدهد:
|
|||
|
|
|||
|
- **کل برنامهها:** تعداد کل برنامههای ثبت شده
|
|||
|
- **فعال:** تعداد برنامههای فعال
|
|||
|
- **غیرفعال:** تعداد برنامههای غیرفعال
|
|||
|
- **جدید (7 روز):** برنامههای ایجاد شده در هفته گذشته
|
|||
|
|
|||
|
### 3. ایجاد برنامه جدید
|
|||
|
|
|||
|
#### مراحل ایجاد:
|
|||
|
|
|||
|
1. **کلیک روی "ایجاد برنامه جدید"**
|
|||
|
2. **پر کردن فرم:**
|
|||
|
- **نام برنامه:** نام منحصر به فرد برنامه
|
|||
|
- **توضیحات:** توضیح کاربرد برنامه
|
|||
|
- **آدرس وبسایت:** URL اصلی برنامه
|
|||
|
- **آدرس بازگشت:** URL callback برنامه
|
|||
|
- **محدودیت درخواست:** تعداد درخواست مجاز در ساعت
|
|||
|
|
|||
|
#### تنظیمات امنیتی:
|
|||
|
|
|||
|
##### IP Whitelist:
|
|||
|
```
|
|||
|
- در صورت خالی بودن: از هر IP مجاز است
|
|||
|
- افزودن IP: 192.168.1.1 یا 192.168.1.0/24
|
|||
|
- پشتیبانی از CIDR notation
|
|||
|
- Validation خودکار آدرسهای IP
|
|||
|
```
|
|||
|
|
|||
|
##### Scope Management:
|
|||
|
```
|
|||
|
read_profile - دسترسی به اطلاعات پروفایل
|
|||
|
write_profile - تغییر اطلاعات پروفایل
|
|||
|
read_business - دسترسی به اطلاعات کسب و کار
|
|||
|
write_business - تغییر اطلاعات کسب و کار
|
|||
|
read_financial - دسترسی به اطلاعات مالی
|
|||
|
write_financial - تغییر اطلاعات مالی
|
|||
|
read_contacts - دسترسی به لیست مخاطبین
|
|||
|
write_contacts - تغییر لیست مخاطبین
|
|||
|
read_documents - دسترسی به اسناد
|
|||
|
write_documents - تغییر اسناد
|
|||
|
admin_access - دسترسی مدیریتی
|
|||
|
```
|
|||
|
|
|||
|
### 4. مدیریت برنامهها
|
|||
|
|
|||
|
#### کارت برنامه:
|
|||
|
- **وضعیت:** فعال/غیرفعال با رنگبندی
|
|||
|
- **Client ID:** شناسه یکتا برنامه
|
|||
|
- **تاریخ ایجاد:** تاریخ ثبت برنامه
|
|||
|
- **توضیحات:** شرح برنامه
|
|||
|
|
|||
|
#### عملیات موجود:
|
|||
|
|
|||
|
##### دکمههای اصلی:
|
|||
|
- **ویرایش:** تغییر اطلاعات برنامه
|
|||
|
- **آمار:** مشاهده آمار استفاده
|
|||
|
- **فعال/غیرفعال:** تغییر وضعیت برنامه
|
|||
|
|
|||
|
##### منوی سه نقطه:
|
|||
|
- **بازسازی کلید:** تولید Client Secret جدید
|
|||
|
- **لغو توکنها:** لغو تمام توکنهای فعال
|
|||
|
- **حذف:** حذف کامل برنامه
|
|||
|
|
|||
|
### 5. اطلاعات امنیتی
|
|||
|
|
|||
|
پس از ایجاد برنامه، اطلاعات زیر نمایش داده میشود:
|
|||
|
|
|||
|
```
|
|||
|
Client ID: mL0qT1fkIL6MCJfxIPAh7nM2cQ7ykxEy
|
|||
|
Client Secret: goM7latD9akY83z2O2e9IIEYED3Re6sRMd36f5cUSYHm389PPSqYbFHSX8GtQ9H1
|
|||
|
```
|
|||
|
|
|||
|
⚠️ **هشدار:** این اطلاعات را در جای امنی ذخیره کنید!
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
## 👤 بخش کاربری
|
|||
|
|
|||
|
### 1. صفحه مجوزدهی
|
|||
|
|
|||
|
هنگام اتصال برنامه خارجی، کاربر به صفحه زیر هدایت میشود:
|
|||
|
|
|||
|
```
|
|||
|
┌─────────────────────────────────────┐
|
|||
|
│ مجوزدهی OAuth │
|
|||
|
├─────────────────────────────────────┤
|
|||
|
│ │
|
|||
|
│ [آیکون برنامه] نام برنامه │
|
|||
|
│ توضیحات برنامه... │
|
|||
|
│ │
|
|||
|
│ این برنامه درخواست دسترسی به: │
|
|||
|
│ ✓ خواندن اطلاعات پروفایل │
|
|||
|
│ ✓ خواندن اطلاعات کسب و کار │
|
|||
|
│ │
|
|||
|
│ [لغو] [تأیید] │
|
|||
|
└─────────────────────────────────────┘
|
|||
|
```
|
|||
|
|
|||
|
### 2. اطلاعات نمایش داده شده:
|
|||
|
|
|||
|
- **نام و لوگوی برنامه**
|
|||
|
- **توضیحات برنامه**
|
|||
|
- **محدودههای دسترسی درخواستی**
|
|||
|
- **دکمههای تأیید/لغو**
|
|||
|
|
|||
|
### 3. تصمیم کاربر:
|
|||
|
|
|||
|
- **تأیید:** ادامه فرآیند OAuth
|
|||
|
- **لغو:** بازگشت به برنامه اصلی
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
## 📡 API Documentation
|
|||
|
|
|||
|
### Base URL
|
|||
|
```
|
|||
|
https://your-domain.com/oauth
|
|||
|
```
|
|||
|
|
|||
|
### 1. Authorization Endpoint
|
|||
|
|
|||
|
#### درخواست مجوز:
|
|||
|
```http
|
|||
|
GET /oauth/authorize
|
|||
|
```
|
|||
|
|
|||
|
#### پارامترهای مورد نیاز:
|
|||
|
```javascript
|
|||
|
{
|
|||
|
"response_type": "code",
|
|||
|
"client_id": "mL0qT1fkIL6MCJfxIPAh7nM2cQ7ykxEy",
|
|||
|
"redirect_uri": "https://your-app.com/callback",
|
|||
|
"scope": "read_profile read_business",
|
|||
|
"state": "random_string_for_csrf"
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### پاسخ موفق:
|
|||
|
```http
|
|||
|
HTTP/1.1 302 Found
|
|||
|
Location: https://your-app.com/callback?code=AUTHORIZATION_CODE&state=random_string
|
|||
|
```
|
|||
|
|
|||
|
### 2. Token Endpoint
|
|||
|
|
|||
|
#### درخواست Access Token:
|
|||
|
```http
|
|||
|
POST /oauth/token
|
|||
|
Content-Type: application/x-www-form-urlencoded
|
|||
|
```
|
|||
|
|
|||
|
#### پارامترهای مورد نیاز:
|
|||
|
```javascript
|
|||
|
{
|
|||
|
"grant_type": "authorization_code",
|
|||
|
"client_id": "mL0qT1fkIL6MCJfxIPAh7nM2cQ7ykxEy",
|
|||
|
"client_secret": "goM7latD9akY83z2O2e9IIEYED3Re6sRMd36f5cUSYHm389PPSqYbFHSX8GtQ9H1",
|
|||
|
"code": "AUTHORIZATION_CODE",
|
|||
|
"redirect_uri": "https://your-app.com/callback"
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### پاسخ موفق:
|
|||
|
```json
|
|||
|
{
|
|||
|
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
|
|||
|
"token_type": "Bearer",
|
|||
|
"expires_in": 3600,
|
|||
|
"refresh_token": "def50200...",
|
|||
|
"scope": "read_profile read_business"
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 3. User Info Endpoint
|
|||
|
|
|||
|
#### درخواست اطلاعات کاربر:
|
|||
|
```http
|
|||
|
GET /oauth/userinfo
|
|||
|
Authorization: Bearer ACCESS_TOKEN
|
|||
|
```
|
|||
|
|
|||
|
#### پاسخ موفق:
|
|||
|
```json
|
|||
|
{
|
|||
|
"id": 123,
|
|||
|
"email": "user@example.com",
|
|||
|
"name": "نام کاربر",
|
|||
|
"profile": {
|
|||
|
"phone": "+989123456789",
|
|||
|
"address": "تهران، ایران"
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 4. Refresh Token Endpoint
|
|||
|
|
|||
|
#### تمدید Access Token:
|
|||
|
```http
|
|||
|
POST /oauth/token
|
|||
|
Content-Type: application/x-www-form-urlencoded
|
|||
|
```
|
|||
|
|
|||
|
#### پارامترهای مورد نیاز:
|
|||
|
```javascript
|
|||
|
{
|
|||
|
"grant_type": "refresh_token",
|
|||
|
"client_id": "mL0qT1fkIL6MCJfxIPAh7nM2cQ7ykxEy",
|
|||
|
"client_secret": "goM7latD9akY83z2O2e9IIEYED3Re6sRMd36f5cUSYHm389PPSqYbFHSX8GtQ9H1",
|
|||
|
"refresh_token": "def50200..."
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 5. Revoke Endpoint
|
|||
|
|
|||
|
#### لغو Token:
|
|||
|
```http
|
|||
|
POST /oauth/revoke
|
|||
|
Authorization: Bearer ACCESS_TOKEN
|
|||
|
```
|
|||
|
|
|||
|
#### پارامترهای مورد نیاز:
|
|||
|
```javascript
|
|||
|
{
|
|||
|
"token": "ACCESS_TOKEN_OR_REFRESH_TOKEN"
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 6. Discovery Endpoint
|
|||
|
|
|||
|
#### دریافت اطلاعات OAuth Server:
|
|||
|
```http
|
|||
|
GET /.well-known/oauth-authorization-server
|
|||
|
```
|
|||
|
|
|||
|
#### پاسخ:
|
|||
|
```json
|
|||
|
{
|
|||
|
"issuer": "https://hesabix.ir",
|
|||
|
"authorization_endpoint": "https://hesabix.ir/oauth/authorize",
|
|||
|
"token_endpoint": "https://hesabix.ir/oauth/token",
|
|||
|
"userinfo_endpoint": "https://hesabix.ir/oauth/userinfo",
|
|||
|
"revocation_endpoint": "https://hesabix.ir/oauth/revoke",
|
|||
|
"response_types_supported": ["code"],
|
|||
|
"grant_types_supported": ["authorization_code", "refresh_token"],
|
|||
|
"token_endpoint_auth_methods_supported": ["client_secret_post"],
|
|||
|
"scopes_supported": [
|
|||
|
"read_profile",
|
|||
|
"write_profile",
|
|||
|
"read_business",
|
|||
|
"write_business",
|
|||
|
"read_financial",
|
|||
|
"write_financial",
|
|||
|
"read_contacts",
|
|||
|
"write_contacts",
|
|||
|
"read_documents",
|
|||
|
"write_documents",
|
|||
|
"admin_access"
|
|||
|
]
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
## 🔗 نحوه اتصال
|
|||
|
|
|||
|
### 1. ثبت برنامه
|
|||
|
|
|||
|
ابتدا برنامه خود را در بخش مدیریت ثبت کنید:
|
|||
|
|
|||
|
```javascript
|
|||
|
// اطلاعات مورد نیاز
|
|||
|
const appInfo = {
|
|||
|
name: "My Application",
|
|||
|
description: "توضیح برنامه من",
|
|||
|
website: "https://myapp.com",
|
|||
|
redirectUri: "https://myapp.com/oauth/callback",
|
|||
|
allowedScopes: ["read_profile", "read_business"],
|
|||
|
ipWhitelist: ["192.168.1.0/24"], // اختیاری
|
|||
|
rateLimit: 1000
|
|||
|
};
|
|||
|
```
|
|||
|
|
|||
|
### 2. پیادهسازی OAuth Flow
|
|||
|
|
|||
|
#### مرحله 1: درخواست مجوز
|
|||
|
```javascript
|
|||
|
function initiateOAuth() {
|
|||
|
const params = new URLSearchParams({
|
|||
|
response_type: 'code',
|
|||
|
client_id: 'YOUR_CLIENT_ID',
|
|||
|
redirect_uri: 'https://myapp.com/oauth/callback',
|
|||
|
scope: 'read_profile read_business',
|
|||
|
state: generateRandomString()
|
|||
|
});
|
|||
|
|
|||
|
window.location.href = `https://hesabix.com/oauth/authorize?${params}`;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### مرحله 2: دریافت Authorization Code
|
|||
|
```javascript
|
|||
|
// در callback URL
|
|||
|
function handleCallback() {
|
|||
|
const urlParams = new URLSearchParams(window.location.search);
|
|||
|
const code = urlParams.get('code');
|
|||
|
const state = urlParams.get('state');
|
|||
|
|
|||
|
if (code && state) {
|
|||
|
exchangeCodeForToken(code);
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### مرحله 3: تبدیل Code به Token
|
|||
|
```javascript
|
|||
|
async function exchangeCodeForToken(code) {
|
|||
|
const response = await fetch('https://hesabix.com/oauth/token', {
|
|||
|
method: 'POST',
|
|||
|
headers: {
|
|||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|||
|
},
|
|||
|
body: new URLSearchParams({
|
|||
|
grant_type: 'authorization_code',
|
|||
|
client_id: 'YOUR_CLIENT_ID',
|
|||
|
client_secret: 'YOUR_CLIENT_SECRET',
|
|||
|
code: code,
|
|||
|
redirect_uri: 'https://myapp.com/oauth/callback'
|
|||
|
})
|
|||
|
});
|
|||
|
|
|||
|
const tokenData = await response.json();
|
|||
|
// ذخیره token
|
|||
|
localStorage.setItem('access_token', tokenData.access_token);
|
|||
|
localStorage.setItem('refresh_token', tokenData.refresh_token);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### مرحله 4: استفاده از Access Token
|
|||
|
```javascript
|
|||
|
async function getUserInfo() {
|
|||
|
const response = await fetch('https://hesabix.com/oauth/userinfo', {
|
|||
|
headers: {
|
|||
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
const userData = await response.json();
|
|||
|
return userData;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 3. مدیریت Token
|
|||
|
|
|||
|
#### تمدید Access Token:
|
|||
|
```javascript
|
|||
|
async function refreshAccessToken() {
|
|||
|
const response = await fetch('https://hesabix.com/oauth/token', {
|
|||
|
method: 'POST',
|
|||
|
headers: {
|
|||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|||
|
},
|
|||
|
body: new URLSearchParams({
|
|||
|
grant_type: 'refresh_token',
|
|||
|
client_id: 'YOUR_CLIENT_ID',
|
|||
|
client_secret: 'YOUR_CLIENT_SECRET',
|
|||
|
refresh_token: localStorage.getItem('refresh_token')
|
|||
|
})
|
|||
|
});
|
|||
|
|
|||
|
const tokenData = await response.json();
|
|||
|
localStorage.setItem('access_token', tokenData.access_token);
|
|||
|
localStorage.setItem('refresh_token', tokenData.refresh_token);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### لغو Token:
|
|||
|
```javascript
|
|||
|
async function revokeToken() {
|
|||
|
await fetch('https://hesabix.com/oauth/revoke', {
|
|||
|
method: 'POST',
|
|||
|
headers: {
|
|||
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
|||
|
},
|
|||
|
body: JSON.stringify({
|
|||
|
token: localStorage.getItem('access_token')
|
|||
|
})
|
|||
|
});
|
|||
|
|
|||
|
localStorage.removeItem('access_token');
|
|||
|
localStorage.removeItem('refresh_token');
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
## 🔒 امنیت
|
|||
|
|
|||
|
### 1. محدودیتهای IP
|
|||
|
|
|||
|
```javascript
|
|||
|
// بررسی IP در backend
|
|||
|
function checkIpWhitelist($clientIp, $whitelist) {
|
|||
|
if (empty($whitelist)) {
|
|||
|
return true; // همه IP ها مجاز
|
|||
|
}
|
|||
|
|
|||
|
foreach ($whitelist as $allowedIp) {
|
|||
|
if (ipInRange($clientIp, $allowedIp)) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 2. محدودیتهای Scope
|
|||
|
|
|||
|
```javascript
|
|||
|
// بررسی دسترسی در backend
|
|||
|
function checkScope($requestedScope, $allowedScopes) {
|
|||
|
$requestedScopes = explode(' ', $requestedScope);
|
|||
|
|
|||
|
foreach ($requestedScopes as $scope) {
|
|||
|
if (!in_array($scope, $allowedScopes)) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 3. Rate Limiting
|
|||
|
|
|||
|
```javascript
|
|||
|
// محدودیت درخواست
|
|||
|
function checkRateLimit($clientId, $limit) {
|
|||
|
$requests = getRequestCount($clientId, '1 hour');
|
|||
|
return $requests < $limit;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 4. Token Security
|
|||
|
|
|||
|
- **Access Token:** عمر 1 ساعت
|
|||
|
- **Refresh Token:** عمر 30 روز
|
|||
|
- **JWT Signature:** امضای دیجیتال
|
|||
|
- **Token Revocation:** امکان لغو فوری
|
|||
|
|
|||
|
### 5. نکات امنیتی مهم
|
|||
|
|
|||
|
1. **HTTPS اجباری:** تمام ارتباطات باید روی HTTPS باشد
|
|||
|
2. **State Parameter:** همیشه از state parameter استفاده کنید
|
|||
|
3. **Client Secret:** Client Secret را در کد سمت کلاینت قرار ندهید
|
|||
|
4. **Token Storage:** توکنها را در جای امنی ذخیره کنید
|
|||
|
5. **Scope Validation:** همیشه scope ها را بررسی کنید
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
## 💻 مثالهای عملی
|
|||
|
|
|||
|
### مثال 1: اپلیکیشن وب
|
|||
|
|
|||
|
```html
|
|||
|
<!DOCTYPE html>
|
|||
|
<html>
|
|||
|
<head>
|
|||
|
<title>OAuth Example</title>
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<button onclick="login()">ورود با Hesabix</button>
|
|||
|
|
|||
|
<script>
|
|||
|
const CLIENT_ID = 'YOUR_CLIENT_ID';
|
|||
|
const REDIRECT_URI = 'https://myapp.com/callback';
|
|||
|
|
|||
|
function login() {
|
|||
|
const params = new URLSearchParams({
|
|||
|
response_type: 'code',
|
|||
|
client_id: CLIENT_ID,
|
|||
|
redirect_uri: REDIRECT_URI,
|
|||
|
scope: 'read_profile',
|
|||
|
state: Math.random().toString(36)
|
|||
|
});
|
|||
|
|
|||
|
window.location.href = `https://hesabix.com/oauth/authorize?${params}`;
|
|||
|
}
|
|||
|
|
|||
|
// بررسی callback
|
|||
|
if (window.location.search.includes('code=')) {
|
|||
|
handleCallback();
|
|||
|
}
|
|||
|
|
|||
|
async function handleCallback() {
|
|||
|
const code = new URLSearchParams(window.location.search).get('code');
|
|||
|
|
|||
|
const response = await fetch('https://hesabix.com/oauth/token', {
|
|||
|
method: 'POST',
|
|||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|||
|
body: new URLSearchParams({
|
|||
|
grant_type: 'authorization_code',
|
|||
|
client_id: CLIENT_ID,
|
|||
|
client_secret: 'YOUR_CLIENT_SECRET',
|
|||
|
code: code,
|
|||
|
redirect_uri: REDIRECT_URI
|
|||
|
})
|
|||
|
});
|
|||
|
|
|||
|
const data = await response.json();
|
|||
|
console.log('Token received:', data);
|
|||
|
}
|
|||
|
</script>
|
|||
|
</body>
|
|||
|
</html>
|
|||
|
```
|
|||
|
|
|||
|
### مثال 2: اپلیکیشن موبایل
|
|||
|
|
|||
|
```javascript
|
|||
|
// React Native Example
|
|||
|
import { Linking } from 'react-native';
|
|||
|
|
|||
|
class OAuthManager {
|
|||
|
constructor() {
|
|||
|
this.clientId = 'YOUR_CLIENT_ID';
|
|||
|
this.redirectUri = 'myapp://oauth/callback';
|
|||
|
}
|
|||
|
|
|||
|
async login() {
|
|||
|
const authUrl = `https://hesabix.com/oauth/authorize?` +
|
|||
|
`response_type=code&` +
|
|||
|
`client_id=${this.clientId}&` +
|
|||
|
`redirect_uri=${encodeURIComponent(this.redirectUri)}&` +
|
|||
|
`scope=read_profile&` +
|
|||
|
`state=${Math.random().toString(36)}`;
|
|||
|
|
|||
|
await Linking.openURL(authUrl);
|
|||
|
}
|
|||
|
|
|||
|
async handleCallback(url) {
|
|||
|
const code = url.match(/code=([^&]*)/)?.[1];
|
|||
|
if (code) {
|
|||
|
await this.exchangeCodeForToken(code);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
async exchangeCodeForToken(code) {
|
|||
|
const response = await fetch('https://hesabix.com/oauth/token', {
|
|||
|
method: 'POST',
|
|||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|||
|
body: new URLSearchParams({
|
|||
|
grant_type: 'authorization_code',
|
|||
|
client_id: this.clientId,
|
|||
|
client_secret: 'YOUR_CLIENT_SECRET',
|
|||
|
code: code,
|
|||
|
redirect_uri: this.redirectUri
|
|||
|
})
|
|||
|
});
|
|||
|
|
|||
|
const data = await response.json();
|
|||
|
await AsyncStorage.setItem('access_token', data.access_token);
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### مثال 3: اپلیکیشن سرور
|
|||
|
|
|||
|
```python
|
|||
|
# Python Flask Example
|
|||
|
from flask import Flask, request, redirect, session
|
|||
|
import requests
|
|||
|
|
|||
|
app = Flask(__name__)
|
|||
|
app.secret_key = 'your-secret-key'
|
|||
|
|
|||
|
CLIENT_ID = 'YOUR_CLIENT_ID'
|
|||
|
CLIENT_SECRET = 'YOUR_CLIENT_SECRET'
|
|||
|
REDIRECT_URI = 'https://myapp.com/oauth/callback'
|
|||
|
|
|||
|
@app.route('/login')
|
|||
|
def login():
|
|||
|
auth_url = f'https://hesabix.com/oauth/authorize?' + \
|
|||
|
f'response_type=code&' + \
|
|||
|
f'client_id={CLIENT_ID}&' + \
|
|||
|
f'redirect_uri={REDIRECT_URI}&' + \
|
|||
|
f'scope=read_profile'
|
|||
|
|
|||
|
return redirect(auth_url)
|
|||
|
|
|||
|
@app.route('/oauth/callback')
|
|||
|
def callback():
|
|||
|
code = request.args.get('code')
|
|||
|
|
|||
|
# تبدیل code به token
|
|||
|
token_response = requests.post('https://hesabix.com/oauth/token', data={
|
|||
|
'grant_type': 'authorization_code',
|
|||
|
'client_id': CLIENT_ID,
|
|||
|
'client_secret': CLIENT_SECRET,
|
|||
|
'code': code,
|
|||
|
'redirect_uri': REDIRECT_URI
|
|||
|
})
|
|||
|
|
|||
|
token_data = token_response.json()
|
|||
|
session['access_token'] = token_data['access_token']
|
|||
|
|
|||
|
return 'Login successful!'
|
|||
|
|
|||
|
@app.route('/user-info')
|
|||
|
def user_info():
|
|||
|
headers = {'Authorization': f"Bearer {session['access_token']}"}
|
|||
|
response = requests.get('https://hesabix.com/oauth/userinfo', headers=headers)
|
|||
|
|
|||
|
return response.json()
|
|||
|
```
|
|||
|
|
|||
|
### مثال 4: کلاس کامل JavaScript
|
|||
|
|
|||
|
```javascript
|
|||
|
class HesabixOAuth {
|
|||
|
constructor(clientId, redirectUri) {
|
|||
|
this.clientId = clientId;
|
|||
|
this.redirectUri = redirectUri;
|
|||
|
this.baseUrl = 'https://hesabix.com';
|
|||
|
}
|
|||
|
|
|||
|
// شروع فرآیند OAuth
|
|||
|
authorize(scopes = ['read_profile']) {
|
|||
|
const state = this.generateState();
|
|||
|
const params = new URLSearchParams({
|
|||
|
client_id: this.clientId,
|
|||
|
redirect_uri: this.redirectUri,
|
|||
|
response_type: 'code',
|
|||
|
scope: scopes.join(' '),
|
|||
|
state: state
|
|||
|
});
|
|||
|
|
|||
|
localStorage.setItem('oauth_state', state);
|
|||
|
window.location.href = `${this.baseUrl}/oauth/authorize?${params}`;
|
|||
|
}
|
|||
|
|
|||
|
// پردازش callback
|
|||
|
async handleCallback() {
|
|||
|
const urlParams = new URLSearchParams(window.location.search);
|
|||
|
const code = urlParams.get('code');
|
|||
|
const state = urlParams.get('state');
|
|||
|
const savedState = localStorage.getItem('oauth_state');
|
|||
|
|
|||
|
if (state !== savedState) {
|
|||
|
throw new Error('State mismatch');
|
|||
|
}
|
|||
|
|
|||
|
if (!code) {
|
|||
|
throw new Error('Authorization code not found');
|
|||
|
}
|
|||
|
|
|||
|
const tokens = await this.exchangeCode(code);
|
|||
|
localStorage.setItem('access_token', tokens.access_token);
|
|||
|
localStorage.setItem('refresh_token', tokens.refresh_token);
|
|||
|
|
|||
|
return tokens;
|
|||
|
}
|
|||
|
|
|||
|
// مبادله کد با توکن
|
|||
|
async exchangeCode(code) {
|
|||
|
const response = await fetch(`${this.baseUrl}/oauth/token`, {
|
|||
|
method: 'POST',
|
|||
|
headers: {
|
|||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|||
|
},
|
|||
|
body: new URLSearchParams({
|
|||
|
grant_type: 'authorization_code',
|
|||
|
client_id: this.clientId,
|
|||
|
client_secret: 'YOUR_CLIENT_SECRET', // در سرور ذخیره شود
|
|||
|
code: code,
|
|||
|
redirect_uri: this.redirectUri
|
|||
|
})
|
|||
|
});
|
|||
|
|
|||
|
if (!response.ok) {
|
|||
|
throw new Error('Token exchange failed');
|
|||
|
}
|
|||
|
|
|||
|
return await response.json();
|
|||
|
}
|
|||
|
|
|||
|
// دریافت اطلاعات کاربر
|
|||
|
async getUserInfo() {
|
|||
|
const token = localStorage.getItem('access_token');
|
|||
|
const response = await fetch(`${this.baseUrl}/oauth/userinfo`, {
|
|||
|
headers: {
|
|||
|
'Authorization': `Bearer ${token}`
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
if (!response.ok) {
|
|||
|
throw new Error('Failed to get user info');
|
|||
|
}
|
|||
|
|
|||
|
return await response.json();
|
|||
|
}
|
|||
|
|
|||
|
// تمدید توکن
|
|||
|
async refreshToken() {
|
|||
|
const refreshToken = localStorage.getItem('refresh_token');
|
|||
|
const response = await fetch(`${this.baseUrl}/oauth/token`, {
|
|||
|
method: 'POST',
|
|||
|
headers: {
|
|||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|||
|
},
|
|||
|
body: new URLSearchParams({
|
|||
|
grant_type: 'refresh_token',
|
|||
|
client_id: this.clientId,
|
|||
|
client_secret: 'YOUR_CLIENT_SECRET',
|
|||
|
refresh_token: refreshToken
|
|||
|
})
|
|||
|
});
|
|||
|
|
|||
|
if (!response.ok) {
|
|||
|
throw new Error('Token refresh failed');
|
|||
|
}
|
|||
|
|
|||
|
const tokens = await response.json();
|
|||
|
localStorage.setItem('access_token', tokens.access_token);
|
|||
|
localStorage.setItem('refresh_token', tokens.refresh_token);
|
|||
|
|
|||
|
return tokens;
|
|||
|
}
|
|||
|
|
|||
|
generateState() {
|
|||
|
return Math.random().toString(36).substring(2, 15);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// استفاده
|
|||
|
const oauth = new HesabixOAuth('YOUR_CLIENT_ID', 'https://yourapp.com/callback');
|
|||
|
|
|||
|
// شروع OAuth
|
|||
|
oauth.authorize(['read_profile', 'read_business']);
|
|||
|
|
|||
|
// در صفحه callback
|
|||
|
oauth.handleCallback().then(tokens => {
|
|||
|
console.log('OAuth successful:', tokens);
|
|||
|
// دریافت اطلاعات کاربر
|
|||
|
return oauth.getUserInfo();
|
|||
|
}).then(userInfo => {
|
|||
|
console.log('User info:', userInfo);
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
## 🔧 عیبیابی
|
|||
|
|
|||
|
### خطاهای رایج:
|
|||
|
|
|||
|
#### 1. invalid_client
|
|||
|
**علت:** Client ID یا Secret اشتباه
|
|||
|
**راه حل:** بررسی صحت Client ID و Secret
|
|||
|
|
|||
|
#### 2. invalid_grant
|
|||
|
**علت:** Authorization Code نامعتبر یا منقضی شده
|
|||
|
**راه حل:** درخواست مجدد Authorization Code
|
|||
|
|
|||
|
#### 3. invalid_scope
|
|||
|
**علت:** Scope درخواستی مجاز نیست
|
|||
|
**راه حل:** بررسی Scope های مجاز در پنل مدیریت
|
|||
|
|
|||
|
#### 4. access_denied
|
|||
|
**علت:** کاربر دسترسی را لغو کرده
|
|||
|
**راه حل:** درخواست مجدد از کاربر
|
|||
|
|
|||
|
#### 5. server_error
|
|||
|
**علت:** خطای سرور
|
|||
|
**راه حل:** بررسی لاگهای سرور
|
|||
|
|
|||
|
### کدهای خطا:
|
|||
|
|
|||
|
```javascript
|
|||
|
const errorCodes = {
|
|||
|
'invalid_request': 'درخواست نامعتبر',
|
|||
|
'invalid_client': 'Client ID یا Secret اشتباه',
|
|||
|
'invalid_grant': 'کد مجوز نامعتبر',
|
|||
|
'unauthorized_client': 'برنامه مجاز نیست',
|
|||
|
'unsupported_grant_type': 'نوع grant پشتیبانی نمیشود',
|
|||
|
'invalid_scope': 'Scope نامعتبر',
|
|||
|
'access_denied': 'دسترسی رد شد',
|
|||
|
'server_error': 'خطای سرور',
|
|||
|
'temporarily_unavailable': 'سرویس موقتاً در دسترس نیست'
|
|||
|
};
|
|||
|
```
|
|||
|
|
|||
|
### لاگها:
|
|||
|
|
|||
|
```bash
|
|||
|
# مشاهده لاگهای OAuth
|
|||
|
tail -f /var/log/hesabix/oauth.log
|
|||
|
|
|||
|
# پاک کردن کش
|
|||
|
php bin/console cache:clear
|
|||
|
|
|||
|
# بررسی وضعیت سرور
|
|||
|
php bin/console debug:router | grep oauth
|
|||
|
```
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
## 📞 پشتیبانی
|
|||
|
|
|||
|
### اطلاعات تماس:
|
|||
|
|
|||
|
- **ایمیل:** support@hesabix.com
|
|||
|
- **تلفن:** 021-12345678
|
|||
|
- **ساعات کاری:** شنبه تا چهارشنبه، 9 صبح تا 6 عصر
|
|||
|
- **تلگرام:** @hesabix_support
|
|||
|
|
|||
|
### سوالات متداول:
|
|||
|
|
|||
|
#### Q: چگونه Client Secret را تغییر دهم؟
|
|||
|
A: در پنل مدیریت، روی منوی سه نقطه برنامه کلیک کرده و "بازسازی کلید" را انتخاب کنید.
|
|||
|
|
|||
|
#### Q: آیا میتوانم چندین Redirect URI داشته باشم؟
|
|||
|
A: خیر، هر برنامه فقط یک Redirect URI میتواند داشته باشد.
|
|||
|
|
|||
|
#### Q: توکنها چه مدت اعتبار دارند؟
|
|||
|
A: Access Token 1 ساعت و Refresh Token 30 روز اعتبار دارد.
|
|||
|
|
|||
|
#### Q: چگونه IP Whitelist را تنظیم کنم؟
|
|||
|
A: در زمان ایجاد یا ویرایش برنامه، IP های مجاز را اضافه کنید. اگر خالی باشد، از هر IP مجاز است.
|
|||
|
|
|||
|
#### Q: آیا میتوانم Scope ها را بعداً تغییر دهم؟
|
|||
|
A: بله، در بخش ویرایش برنامه میتوانید Scope ها را تغییر دهید.
|
|||
|
|
|||
|
### گزارش باگ:
|
|||
|
|
|||
|
برای گزارش باگ، لطفاً اطلاعات زیر را ارسال کنید:
|
|||
|
|
|||
|
1. **نوع خطا:** کد خطا و پیام
|
|||
|
2. **مراحل تولید:** مراحل دقیق تولید خطا
|
|||
|
3. **اطلاعات برنامه:** Client ID و نام برنامه
|
|||
|
4. **لاگها:** لاگهای مربوطه
|
|||
|
5. **مرورگر/سیستم عامل:** اطلاعات محیط اجرا
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
## 📚 منابع بیشتر
|
|||
|
|
|||
|
- [RFC 6749 - OAuth 2.0](https://tools.ietf.org/html/rfc6749)
|
|||
|
- [OAuth 2.0 Security Best Practices](https://tools.ietf.org/html/draft-ietf-oauth-security-topics)
|
|||
|
- [OpenID Connect](https://openid.net/connect/)
|
|||
|
- [OAuth 2.0 Authorization Code Flow](https://auth0.com/docs/protocols/oauth2/oauth2-authorization-code-flow)
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
## 📋 چکلیست پیادهسازی
|
|||
|
|
|||
|
### قبل از شروع:
|
|||
|
- [ ] برنامه در پنل مدیریت ثبت شده
|
|||
|
- [ ] Client ID و Secret دریافت شده
|
|||
|
- [ ] Redirect URI تنظیم شده
|
|||
|
- [ ] Scope های مورد نیاز تعیین شده
|
|||
|
- [ ] IP Whitelist تنظیم شده (در صورت نیاز)
|
|||
|
|
|||
|
### پیادهسازی:
|
|||
|
- [ ] Authorization Request پیادهسازی شده
|
|||
|
- [ ] Callback Handler پیادهسازی شده
|
|||
|
- [ ] Token Exchange پیادهسازی شده
|
|||
|
- [ ] Error Handling پیادهسازی شده
|
|||
|
- [ ] Token Storage پیادهسازی شده
|
|||
|
|
|||
|
### تست:
|
|||
|
- [ ] Authorization Flow تست شده
|
|||
|
- [ ] Token Exchange تست شده
|
|||
|
- [ ] User Info API تست شده
|
|||
|
- [ ] Error Scenarios تست شده
|
|||
|
- [ ] Security Features تست شده
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
**نسخه مستندات:** 1.0
|
|||
|
**تاریخ آخرین بهروزرسانی:** 2025-08-16
|
|||
|
**وضعیت:** فعال ✅
|
|||
|
**توسعهدهنده:** Hesabix Team
|