27 KiB
مستندات کامل سیستم OAuth 2.0 - Hesabix
📋 فهرست مطالب
- معرفی OAuth 2.0
- معماری سیستم
- بخش مدیریت
- بخش کاربری
- API Documentation
- نحوه اتصال
- امنیت
- مثالهای عملی
- عیبیابی
- پشتیبانی
🚀 معرفی 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:
- Client Registration: ثبت برنامه در بخش مدیریت
- Authorization Request: درخواست مجوز از کاربر
- User Consent: تأیید کاربر
- Authorization Code: دریافت کد مجوز
- Token Exchange: تبدیل کد به Access Token
- Resource Access: دسترسی به منابع
پایگاه داده:
-- جداول OAuth
oauth_application -- برنامههای ثبت شده
oauth_scope -- محدودههای دسترسی
oauth_authorization_code -- کدهای مجوز موقت
oauth_access_token -- توکنهای دسترسی
⚙️ بخش مدیریت
1. دسترسی به بخش مدیریت
مدیریت سیستم → تنظیمات سیستم → تب "برنامههای OAuth"
2. آمار کلی
سیستم چهار کارت آمار نمایش میدهد:
- کل برنامهها: تعداد کل برنامههای ثبت شده
- فعال: تعداد برنامههای فعال
- غیرفعال: تعداد برنامههای غیرفعال
- جدید (7 روز): برنامههای ایجاد شده در هفته گذشته
3. ایجاد برنامه جدید
مراحل ایجاد:
- کلیک روی "ایجاد برنامه جدید"
- پر کردن فرم:
- نام برنامه: نام منحصر به فرد برنامه
- توضیحات: توضیح کاربرد برنامه
- آدرس وبسایت: 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
درخواست مجوز:
GET /oauth/authorize
پارامترهای مورد نیاز:
{
"response_type": "code",
"client_id": "mL0qT1fkIL6MCJfxIPAh7nM2cQ7ykxEy",
"redirect_uri": "https://your-app.com/callback",
"scope": "read_profile read_business",
"state": "random_string_for_csrf"
}
پاسخ موفق:
HTTP/1.1 302 Found
Location: https://your-app.com/callback?code=AUTHORIZATION_CODE&state=random_string
2. Token Endpoint
درخواست Access Token:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
پارامترهای مورد نیاز:
{
"grant_type": "authorization_code",
"client_id": "mL0qT1fkIL6MCJfxIPAh7nM2cQ7ykxEy",
"client_secret": "goM7latD9akY83z2O2e9IIEYED3Re6sRMd36f5cUSYHm389PPSqYbFHSX8GtQ9H1",
"code": "AUTHORIZATION_CODE",
"redirect_uri": "https://your-app.com/callback"
}
پاسخ موفق:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "def50200...",
"scope": "read_profile read_business"
}
3. User Info Endpoint
درخواست اطلاعات کاربر:
GET /oauth/userinfo
Authorization: Bearer ACCESS_TOKEN
پاسخ موفق:
{
"id": 123,
"email": "user@example.com",
"name": "نام کاربر",
"profile": {
"phone": "+989123456789",
"address": "تهران، ایران"
}
}
4. Refresh Token Endpoint
تمدید Access Token:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
پارامترهای مورد نیاز:
{
"grant_type": "refresh_token",
"client_id": "mL0qT1fkIL6MCJfxIPAh7nM2cQ7ykxEy",
"client_secret": "goM7latD9akY83z2O2e9IIEYED3Re6sRMd36f5cUSYHm389PPSqYbFHSX8GtQ9H1",
"refresh_token": "def50200..."
}
5. Revoke Endpoint
لغو Token:
POST /oauth/revoke
Authorization: Bearer ACCESS_TOKEN
پارامترهای مورد نیاز:
{
"token": "ACCESS_TOKEN_OR_REFRESH_TOKEN"
}
6. Discovery Endpoint
دریافت اطلاعات OAuth Server:
GET /.well-known/oauth-authorization-server
پاسخ:
{
"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. ثبت برنامه
ابتدا برنامه خود را در بخش مدیریت ثبت کنید:
// اطلاعات مورد نیاز
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: درخواست مجوز
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
// در 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
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
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:
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:
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
// بررسی 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
// بررسی دسترسی در backend
function checkScope($requestedScope, $allowedScopes) {
$requestedScopes = explode(' ', $requestedScope);
foreach ($requestedScopes as $scope) {
if (!in_array($scope, $allowedScopes)) {
return false;
}
}
return true;
}
3. Rate Limiting
// محدودیت درخواست
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. نکات امنیتی مهم
- HTTPS اجباری: تمام ارتباطات باید روی HTTPS باشد
- State Parameter: همیشه از state parameter استفاده کنید
- Client Secret: Client Secret را در کد سمت کلاینت قرار ندهید
- Token Storage: توکنها را در جای امنی ذخیره کنید
- Scope Validation: همیشه scope ها را بررسی کنید
💻 مثالهای عملی
مثال 1: اپلیکیشن وب
<!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: اپلیکیشن موبایل
// 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 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
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
علت: خطای سرور راه حل: بررسی لاگهای سرور
کدهای خطا:
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': 'سرویس موقتاً در دسترس نیست'
};
لاگها:
# مشاهده لاگهای 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 ها را تغییر دهید.
گزارش باگ:
برای گزارش باگ، لطفاً اطلاعات زیر را ارسال کنید:
- نوع خطا: کد خطا و پیام
- مراحل تولید: مراحل دقیق تولید خطا
- اطلاعات برنامه: Client ID و نام برنامه
- لاگها: لاگهای مربوطه
- مرورگر/سیستم عامل: اطلاعات محیط اجرا
📚 منابع بیشتر
- RFC 6749 - OAuth 2.0
- OAuth 2.0 Security Best Practices
- OpenID Connect
- OAuth 2.0 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