📖 درس چهارم: اعتبارسنجی و احراز هویت (Authentication & Authorization)
🎯 هدف این درس: پیادهسازی سیستم احراز هویت حرفهای با استفاده از JWT (JSON Web Token)، مدیریت نقشهای کاربری (Roles/Permissions)، ساخت Middleware امنیتی و محافظت از Endpointهای حساس.
مهمان عزیز! 👋
در درسهای قبل، API خود را با دیتابیس متصل کردیم و عملیات CRUD را پیادهسازی کردیم. اما یک مشکل بزرگ وجود دارد: هر کسی میتواند به تمام Endpointها دسترسی داشته باشد!
در دنیای واقعی، APIها باید بدانند چه کسی درخواست میدهد و آیا او مجوز انجام آن عملیات را دارد یا خیر. در این درس، یک سیستم احراز هویت کامل با JWT پیادهسازی میکنیم و یاد میگیریم چگونه از API خود در برابر دسترسیهای غیرمجاز محافظت کنیم.
💡 JWT چیست؟
JWT یا JSON Web Token، یک استاندارد باز برای انتقال اطلاعات بین دو طرف به صورت امن است. توکن شامل سه بخش است: Header، Payload و Signature. این توکنها خودشان حاوی اطلاعات کاربر هستند و نیازی به ذخیره در سرور ندارند (Stateless).
🔐 ساختار JWT
یک JWT از سه بخش تشکیل شده است که با نقطه (.) از هم جدا میشوند:
- Header: شامل نوع توکن (JWT) و الگوریتم امضا (مثلاً HS256).
- Payload: شامل اطلاعات کاربر (Claims) مثل ID، نقش و تاریخ انقضا.
- Signature: امضای دیجیتال که با یک کلید مخفی ایجاد میشود.
JWT Structure
JSON
// Header (Base64 encoded)
{
"alg": "HS256",
"typ": "JWT"
}
// Payload (Base64 encoded)
{
"user_id": 1,
"email": "younes@example.com",
"role": "admin",
"exp": 1735689600, // زمان انقضا (Timestamp)
"iat": 1735686000 // زمان ایجاد (Timestamp)
}
// Signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
// نهایتاً: header.payload.signature
🗝️ کلاس JWT Handler
یک کلاس اختصاصی برای تولید و اعتبارسنجی توکنهای JWT میسازیم:
JWT.php
PHP
8.2
<?php
class JWT
{
private static $secret = 'your-secret-key-change-this-in-production';
private static $algorithm = 'HS256';
private static $expiry = 3600; // ۱ ساعت
// تولید توکن
public static function generate($payload)
{
$header = json_encode([
'alg' => self::$algorithm,
'typ' => 'JWT'
]);
// اضافه کردن زمانهای استاندارد
$payload['iat'] = time();
$payload['exp'] = time() + self::$expiry;
$headerEncoded = self::base64UrlEncode($header);
$payloadEncoded = self::base64UrlEncode(json_encode($payload));
$signature = hash_hmac(
'sha256',
$headerEncoded . '.' . $payloadEncoded,
self::$secret,
true
);
$signatureEncoded = self::base64UrlEncode($signature);
return $headerEncoded . '.' . $payloadEncoded . '.' . $signatureEncoded;
}
// اعتبارسنجی توکن
public static function validate($token)
{
$parts = explode('.', $token);
if (count($parts) !== 3) {
return false;
}
list($headerEncoded, $payloadEncoded, $signatureEncoded) = $parts;
// بررسی امضا
$signature = self::base64UrlDecode($signatureEncoded);
$expectedSignature = hash_hmac(
'sha256',
$headerEncoded . '.' . $payloadEncoded,
self::$secret,
true
);
if (!hash_equals($signature, $expectedSignature)) {
return false;
}
// دیکد کردن payload
$payload = json_decode(self::base64UrlDecode($payloadEncoded), true);
// بررسی انقضا
if (isset($payload['exp']) && $payload['exp'] < time()) {
return false;
}
return $payload;
}
// دریافت کاربر از توکن
public static function getUser($token)
{
$payload = self::validate($token);
return $payload ? $payload['user_id'] : null;
}
// متدهای کمکی برای Base64 URL Safe
private static function base64UrlEncode($data)
{
return str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode($data)
);
}
private static function base64UrlDecode($data)
{
return base64_decode(
str_replace(
['-', '_'],
['+', '/'],
$data
)
);
}
}
⚠️ هشدار امنیتی:
کلید مخفی (Secret) را در محیط تولید در فایل .env ذخیره کن و هرگز آن را در کد یا مخزن گیت قرار نده. از کلیدهای قوی با طول حداقل ۳۲ کاراکتر استفاده کن.
🔑 Middleware احراز هویت
Middleware یک لایه میانی است که قبل از رسیدن درخواست به کنترلر اجرا میشود. در اینجا یک Middleware برای بررسی توکن JWT میسازیم:
AuthMiddleware.php
PHP
8.2
<?php
require_once __DIR__ . '/../Core/JWT.php';
class AuthMiddleware
{
// بررسی وجود و اعتبار توکن
public static function authenticate()
{
// دریافت هدر Authorization
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? '';
// بررسی وجود توکن
if (empty($authHeader)) {
http_response_code(401);
echo json_encode([
'status' => 'error',
'message' => 'Authorization header is required'
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
// بررسی فرمت Bearer
if (strpos($authHeader, 'Bearer ') !== 0) {
http_response_code(401);
echo json_encode([
'status' => 'error',
'message' => 'Invalid authorization format. Use Bearer token'
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
// استخراج توکن
$token = substr($authHeader, 7);
// اعتبارسنجی توکن
$payload = JWT::validate($token);
if (!$payload) {
http_response_code(401);
echo json_encode([
'status' => 'error',
'message' => 'Invalid or expired token'
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
// ذخیره payload در یک متغیر سراسری برای استفاده در کنترلر
$GLOBALS['auth_user'] = $payload;
return $payload;
}
// بررسی نقش کاربر (Authorization)
public static function authorize($allowedRoles = [])
{
$user = $GLOBALS['auth_user'] ?? null;
if (!$user || empty($allowedRoles)) {
return true;
}
if (!in_array($user['role'], $allowedRoles)) {
http_response_code(403);
echo json_encode([
'status' => 'error',
'message' => 'You do not have permission to access this resource'
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
return true;
}
}
🔐 کنترلر احراز هویت (AuthController)
کنترلر Login و Register را برای تولید توکن JWT پیادهسازی میکنیم:
AuthController.php
PHP
8.2
<?php
require_once __DIR__ . '/../Core/JWT.php';
require_once __DIR__ . '/../Models/User.php';
class AuthController
{
private $userModel;
public function __construct()
{
$this->userModel = new User();
}
// POST /auth/register - ثبتنام کاربر جدید
public function register($request)
{
$data = $request->getBody();
// اعتبارسنجی
$validation = $request->validate([
'name' => 'required|min:3',
'email' => 'required|email',
'password' => 'required|min:6',
'role' => 'required'
]);
if ($validation !== true) {
return Response::error('Validation failed', 422, $validation);
}
// بررسی تکراری نبودن ایمیل
if ($this->userModel->emailExists($data['email'])) {
return Response::error('Email already exists', 422);
}
$user = $this->userModel->create($data);
if (!$user) {
return Response::error('Failed to create user', 500);
}
// تولید توکن
$token = JWT::generate([
'user_id' => $user['id'],
'email' => $user['email'],
'role' => $user['role']
]);
return Response::created([
'user' => $user,
'token' => $token,
'expires_in' => 3600
], 'User registered successfully');
}
// POST /auth/login - ورود کاربر
public function login($request)
{
$data = $request->getBody();
// اعتبارسنجی
$validation = $request->validate([
'email' => 'required|email',
'password' => 'required'
]);
if ($validation !== true) {
return Response::error('Validation failed', 422, $validation);
}
// بررسی وجود کاربر
$user = $this->userModel->findByEmail($data['email']);
if (!$user) {
return Response::error('Invalid credentials', 401);
}
// بررسی رمز عبور
if (!password_verify($data['password'], $user['password'])) {
return Response::error('Invalid credentials', 401);
}
// تولید توکن
$token = JWT::generate([
'user_id' => $user['id'],
'email' => $user['email'],
'role' => $user['role']
]);
return Response::success([
'user' => [
'id' => $user['id'],
'name' => $user['name'],
'email' => $user['email'],
'role' => $user['role']
],
'token' => $token,
'expires_in' => 3600
], 'Login successful');
}
// POST /auth/logout - خروج از سیستم (سمت کلاینت توکن را حذف میکند)
public function logout($request)
{
// در JWT نیازی به عملیات سمت سرور نیست
return Response::success([], 'Logged out successfully. Please remove the token client-side.');
}
// GET /auth/me - دریافت اطلاعات کاربر فعلی
public function me($request)
{
$user = $GLOBALS['auth_user'] ?? null;
if (!$user) {
return Response::error('User not authenticated', 401);
}
// دریافت اطلاعات کامل از دیتابیس
$userData = $this->userModel->find($user['user_id']);
return Response::success($userData);
}
}
🔄 بهروزرسانی مدل User
متد findByEmail را به مدل User اضافه میکنیم:
User.php (افزودهشده)
PHP
8.2
<?php
// اضافه کردن به کلاس User
// دریافت کاربر با ایمیل (برای احراز هویت)
public function findByEmail($email)
{
$stmt = $this->db->prepare(
"SELECT * FROM users WHERE email = :email"
);
$stmt->execute([':email' => $email]);
return $stmt->fetch() ?: null;
}
🗺️ بهروزرسانی مسیرها (index.php)
مسیرهای جدید احراز هویت و محافظت از Endpointها:
index.php (بخش مسیرها)
PHP
8.2
<?php
// ... کدهای قبلی ...
// ========== مسیرهای احراز هویت (عمومی) ==========
$router->add('POST', '/auth/register', 'AuthController', 'register');
$router->add('POST', '/auth/login', 'AuthController', 'login');
// ========== مسیرهای محافظتشده با JWT ==========
// این مسیرها نیاز به احراز هویت دارند
// کاربران (فقط ادمین)
$router->add('GET', '/users', 'UserController', 'index');
$router->add('GET', '/users/{id}', 'UserController', 'show');
$router->add('POST', '/users', 'UserController', 'store');
$router->add('PUT', '/users/{id}', 'UserController', 'update');
$router->add('DELETE', '/users/{id}', 'UserController', 'delete');
// اطلاعات کاربر فعلی (احراز هویت شده)
$router->add('GET', '/auth/me', 'AuthController', 'me');
$router->add('POST', '/auth/logout', 'AuthController', 'logout');
// ========== پردازش درخواست با Middleware ==========
$request = new Request();
$response = $router->dispatch($request);
// ... کدهای بعدی ...
💡 نکته مهم:
برای استفاده از Middleware در مسیرهای محافظتشده، باید قبل از فراخوانی کنترلر، تابع AuthMiddleware::authenticate() را صدا بزنیم. این کار را میتوانیم در خود Router یا با یک لایه اضافی انجام دهیم. برای سادگی، در این درس فرض میکنیم که کنترلرها خودشان Middleware را صدا میزنند.
🛡️ محافظت از کنترلر با Middleware
برای محافظت از Endpointها، کافی است در ابتدای هر متد کنترلر، Middleware را صدا بزنیم:
UserController.php (بخش محافظت)
PHP
8.2
<?php
class UserController
{
private $userModel;
public function __construct()
{
require_once __DIR__ . '/../Models/User.php';
$this->userModel = new User();
}
// GET /users - فقط ادمین
public function index($request)
{
// احراز هویت + بررسی نقش ادمین
require_once __DIR__ . '/../Core/AuthMiddleware.php';
AuthMiddleware::authenticate();
AuthMiddleware::authorize(['admin']);
// ... بقیه کد
}
// GET /users/{id} - ادمین یا خود کاربر
public function show($request, $params)
{
// احراز هویت
require_once __DIR__ . '/../Core/AuthMiddleware.php';
AuthMiddleware::authenticate();
$id = $params[0] ?? null;
$authUser = $GLOBALS['auth_user'];
// اگر ادمین نیست و کاربر دیگری را درخواست کرده، دسترسی ندارد
if ($authUser['role'] !== 'admin' && $authUser['user_id'] != $id) {
return Response::error('You can only access your own profile', 403);
}
// ... بقیه کد
}
// POST /users - فقط ادمین
public function store($request)
{
require_once __DIR__ . '/../Core/AuthMiddleware.php';
AuthMiddleware::authenticate();
AuthMiddleware::authorize(['admin']);
// ... بقیه کد
}
// PUT /users/{id} - ادمین یا خود کاربر
public function update($request, $params)
{
require_once __DIR__ . '/../Core/AuthMiddleware.php';
AuthMiddleware::authenticate();
$id = $params[0] ?? null;
$authUser = $GLOBALS['auth_user'];
if ($authUser['role'] !== 'admin' && $authUser['user_id'] != $id) {
return Response::error('You can only update your own profile', 403);
}
// ... بقیه کد
}
// DELETE /users/{id} - فقط ادمین
public function delete($request, $params)
{
require_once __DIR__ . '/../Core/AuthMiddleware.php';
AuthMiddleware::authenticate();
AuthMiddleware::authorize(['admin']);
// ... بقیه کد
}
}
🔴 خطاهای رایج در احراز هویت
- ❌ اشتباه: ذخیره توکن در localStorage بدون بررسی امنیتی.
- ✅ درست: برای اطلاعات حساس از HttpOnly Cookies استفاده کن.
- ❌ اشتباه: عدم بررسی انقضای توکن در هر درخواست.
- ✅ درست: همیشه توکن را در Middleware اعتبارسنجی کن.
- ❌ اشتباه: استفاده از کلید مخفی ضعیف.
- ✅ درست: از کلیدهای قوی با طول حداقل ۳۲ کاراکتر استفاده کن.
- ❌ اشتباه: ارسال توکن در URL.
- ✅ درست: توکن را در Header Authorization ارسال کن.
💎 نکات کلیدی درس
- ✅ JWT یک استاندارد امن برای احراز هویت بدون نیاز به Session است.
- ✅ توکن JWT شامل Header، Payload و Signature است.
- ✅ Signature با یک کلید مخفی تولید میشود و اعتبار توکن را تضمین میکند.
- ✅ Middleware یک لایه میانی برای بررسی توکن قبل از رسیدن به کنترلر است.
- ✅ Authorization مشخص میکند که هر نقش چه دسترسیهایی دارد.
- ✅ رمز عبور باید با
password_hash()هش شود. - ✅ از
password_verify()برای مقایسه رمز عبور استفاده کن.
🛠️ پروژه عملی درس چهارم
مهمان عزیز، حالا یک سیستم احراز هویت کامل پیادهسازی کن!
مسئله:
یک سیستم احراز هویت کامل برای API محصولات پیادهسازی کن:
- ✅ ثبتنام کاربران با نقشهای مختلف (admin, manager, user)
- ✅ ورود و دریافت توکن JWT
- ✅ محافظت از Endpointهای محصولات
- ✅ نقشهای دسترسی:
- ادمین: تمام عملیات روی محصولات
- مدیر: فقط مشاهده و ایجاد محصول
- کاربر عادی: فقط مشاهده محصولات
- ✅ دریافت اطلاعات کاربر فعلی از طریق توکن
🧪 راهحل پروژه (پاسخ)
مراحل پیادهسازی:
- 1️⃣ کلاس
JWTرا برای تولید و اعتبارسنجی توکن ایجاد کن. - 2️⃣ کلاس
AuthMiddlewareرا برای بررسی توکن و نقشها بساز. - 3️⃣ کنترلر
AuthControllerرا با متدهای register، login، logout و me پیادهسازی کن. - 4️⃣ به مدل
UserمتدfindByEmailرا اضافه کن. - 5️⃣ مسیرهای احراز هویت را در
index.phpثبت کن. - 6️⃣ متدهای
ProductControllerرا با Middleware محافظت کن.
📝 تمرینهای عملی
🧪 تمرین ۱ (ساده):
با استفاده از Postman، فرآیند ثبتنام و ورود را تست کن. توکن دریافت شده را در Header درخواستهای بعدی قرار بده و به Endpoint /auth/me دسترسی پیدا کن.
🧪 تمرین ۲ (متوسط):
سیستم Refresh Token پیادهسازی کن. وقتی توکن منقضی شد، کاربر بتواند با یک توکن Refresh جدید، توکن دسترسی جدید دریافت کند.
🧪 تمرین ۳ (چالشی):
یک سیستم لاگین با IP محدود پیادهسازی کن. اگر کاربر از یک IP جدید وارد شود، نیاز به تأیید دو مرحلهای (OTP) داشته باشد. از تراکنشها برای ذخیره اطلاعات OTP در دیتابیس استفاده کن.
🏁 جمعبندی درس
مهمان عزیز، در این درس یاد گرفتی:
- ✅ JWT چیست و چگونه کار میکند.
- ✅ چگونه یک کلاس JWT کامل برای تولید و اعتبارسنجی توکن بسازی.
- ✅ چگونه Middleware احراز هویت برای محافظت از Endpointها پیادهسازی کنی.
- ✅ چگونه نقشهای کاربری و دسترسیها را مدیریت کنی.
- ✅ چگونه یک کنترلر Auth برای ثبتنام، ورود و خروج بسازی.
- ✅ چگونه از توکن JWT برای احراز هویت در API استفاده کنی.
حالا API شما یک سیستم احراز هویت کامل دارد و Endpointهای حساس محافظت شدهاند. در درس بعدی، امنیت API را با تکنیکهای پیشرفتهتر مثل Rate Limiting، OWASP و جلوگیری از حملات رایج تقویت میکنیم. آمادهای؟ 🚀
🗺️ نقشه راه دوره: شما اینجا هستید!
برای اینکه بدانی دقیقاً کجای مسیر هستی و چه درسهایی در انتظار توست، به جدول زیر نگاه کن. درس فعلی با رنگ متفاوت مشخص شده است.
| درس | عنوان درس | آنچه یاد میگیرید |
|---|---|---|
| ۱ | مفاهیم پایه API | HTTP، REST، معماری Client-Server |
| ۲ | پیادهسازی RESTful API با PHP خام | مسیریابی، متدها، پارامترها، پاسخهای JSON |
| ۳ | مدیریت دادهها و پایگاهداده | اتصال PDO، CRUD، تراکنشها، Pagination |
| ۴ | اعتبارسنجی و احراز هویت | JWT، Roles/Permissions، Middleware |
| ۵ | امنیت API پیشرفته | OWASP Top 10، SQLi، XSS، CSRF، Rate Limiting |
| ۶ | مستندسازی API با OpenAPI | Swagger، OpenAPI Specification، مستندات تعاملی |
| ۷ | Caching و افزایش عملکرد | Redis، ETag، Cache-Control، Query Optimization |
| ۸ | API Versioning و مدیریت چرخه حیات | استراتژیهای نسخهبندی، Deprecation، Backward Compatibility |
| ۹ | تست و دیباگ API حرفهای | PHPUnit، Mocking، Logging، Postman Collection |
| ۱۰ | پروژه نهایی: API فروشگاهی | پروژه کامل فروشگاه اینترنتی با تمام قابلیتها |




نظر خود را بنویسید
با ثبت نظر، به بهبود محتوای ما کمک کنید