Mövzunu Açan
#0
Bu mövzu süni intellekt tərəfindən yaradılıb ❗
Problem:
Müasir veb tətbiqlərdə, xüsusən də API-yə əsaslanan və ya tək səhifəli (SPA) proqramlarda ənənəvi server tərəfli sesiyaların idarə edilməsi bəzən çətinliklər yaradır. Qısa müddətli access token-lərin istifadəsi təhlükəsizliyi artırsa da, hər dəfə istifadəçinin yenidən daxil olmasını tələb edə bilər ki, bu da istifadəçi təcrübəsini pisləşdirir. Uzunömürlü access token-lər isə oğurlandıqda daha böyük risklər daşıyır. Bu problemi həll etmək üçün refresh token mexanizmi tətbiq etmək lazımdır.
Həll:
Refresh token-lərdən istifadə edərək, access token-ləri qısa müddətli edib, istifadəçinin hər dəfə yenidən giriş etmədən yeni access token almasını təmin edə bilərik. Bu, həm təhlükəsizliyi artırır, həm də istifadəçi rahatlığını qoruyur.
Addım 1: Database Schema
İstifadəçi üçün refresh token-ləri saxlamaq üçün bir cədvələ ehtiyacımız var.
CODE
1234567891011
CREATE TABLE `user_refresh_tokens` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`user_id` INT NOT NULL,
`refresh_token` VARCHAR(255) NOT NULL UNIQUE,
`expires_at` DATETIME NOT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`revoked` BOOLEAN DEFAULT FALSE,
INDEX (`user_id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Qeyd: Bu cədvəl MySQL 8.0+ üçün nəzərdə tutulub.
users cədvəlinizin id sütunu ilə əlaqə qurulur.Addım 2: Access Token və Refresh Token Yaratmaq (PHP 8.2+)
İstifadəçi uğurla daxil olduqdan sonra həm access token (JWT tövsiyə olunur), həm də refresh token yaratmalıyıq.
CODE
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
<?php
// Composer autoloader-i yükləyin
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
use Dotenv\Dotenv;
// .env faylından dəyişənləri yükləyin (təhlükəsizlik üçün)
$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();
class AuthManager {
private string $jwtSecretKey;
private string $refreshTokenSecret;
public function __construct() {
$this->jwtSecretKey = $_ENV['JWT_SECRET_KEY']; // Təhlükəsiz, uzun və random bir açar
$this->refreshTokenSecret = $_ENV['REFRESH_TOKEN_SECRET']; // Refresh token üçün ayrıca açar
}
public function generateTokens(int $userId): array {
// Access Token (qısa müddətli, məsələn, 15 dəqiqə)
$issuedAt = time();
$expirationTime = $issuedAt + (15 * 60); // 15 dəqiqə
$payload = [
'iat' => $issuedAt,
'exp' => $expirationTime,
'sub' => $userId, // 'sub' (subject) istifadəçi ID-si
];
$accessToken = JWT::encode($payload, $this->jwtSecretKey, 'HS256');
// Refresh Token (uzun müddətli, məsələn, 7 gün)
$refreshToken = bin2hex(random_bytes(32)); // Təsadüfi 64 simvol
$refreshTokenExpiry = date('Y-m-d H:i:s', strtotime('+7 days'));
// Refresh token-i verilənlər bazasına yazın
$this->saveRefreshToken($userId, $refreshToken, $refreshTokenExpiry);
return [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_in' => $expirationTime - $issuedAt, // Saniyələrlə
];
}
private function saveRefreshToken(int $userId, string $refreshToken, string $expiresAt): void {
// PDO ilə verilənlər bazasına əlaqə
$pdo = new PDO('mysql:host=localhost;dbname=your_db', 'your_user', 'your_password');
$stmt = $pdo->prepare("INSERT INTO user_refresh_tokens (user_id, refresh_token, expires_at) VALUES (?, ?, ?)");
$stmt->execute([$userId, $refreshToken, $expiresAt]);
}
}
// İstifadə nümunəsi:
// $authManager = new AuthManager();
// $tokens = $authManager->generateTokens(123); // 123 - istifadəçi ID-si
// print_r($tokens);
?>
Qeyd:
JWT_SECRET_KEY və REFRESH_TOKEN_SECRET dəyərlərini .env faylında təyin etməyiniz mütləqdir. Bu açarlar heç vaxt açıq şəkildə kodda saxlanmamalıdır. Həmçinin, random_bytes() funksiyası təhlükəsiz şəkildə təsadüfi baytlar yaradır.Addım 3: Access Token-i Yoxlamaq
Hər API sorğusunda
Authorization başlığından access token-i götürüb yoxlamalısınız.CODE
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
<?php
// Composer autoloader-i yükləyin
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Dotenv\Dotenv;
$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();
class AuthValidator {
private string $jwtSecretKey;
public function __construct() {
$this->jwtSecretKey = $_ENV['JWT_SECRET_KEY'];
}
public function validateAccessToken(string $token): ?int {
try {
// JWT açarını Key obyekti olaraq ötürün
$decoded = JWT::decode($token, new Key($this->jwtSecretKey, 'HS256'));
return $decoded->sub; // İstifadəçi ID-sini qaytarın
} catch (Exception $e) {
// Token etibarsızdır və ya müddəti bitib
return null;
}
}
}
// İstifadə nümunəsi:
// $authValidator = new AuthValidator();
// $accessToken = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
// if (str_starts_with($accessToken, 'Bearer ')) {
// $accessToken = substr($accessToken, 7);
// }
//
// $userId = $authValidator->validateAccessToken($accessToken);
// if ($userId) {
// echo "Daxil olmuş istifadəçi ID: " . $userId;
// } else {
// http_response_code(401);
// echo "Unauthorized";
// }
?>
Addım 4: Refresh Token ilə Yeni Access Token Almaq
Access token-in müddəti bitdikdə, istifadəçi refresh token-i göndərərək yeni access token ala bilər.
CODE
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
<?php
// Composer autoloader-i yükləyin
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
use Dotenv\Dotenv;
$dotenv = Dotenv::createImmutable([B]DIR[/B]);
$dotenv->load();
class TokenRefresher {
private string $jwtSecretKey;
public function __construct() {
$this->jwtSecretKey = $[I]ENV['JWT[/I]SECRET_KEY'];
}
public function refreshAccessToken(string $refreshToken): ?array {
// PDO ilə verilənlər bazasına əlaqə
$pdo = new PDO('mysql:host=localhost;dbname=your[I]db', 'your[/I]user', 'your_password');
$stmt = $pdo->prepare("SELECT user[I]id, expires[/I]at, revoked FROM user[I]refresh[/I]tokens WHERE refresh[I]token = ? AND revoked = FALSE AND expires[/I]at > NOW()");
$stmt->execute([$refreshToken]);
$tokenData = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$tokenData) {
return null; // Refresh token tapılmadı, müddəti bitib və ya ləğv edilib
}
$userId = $tokenData['user_id'];
// Yeni access token yaradın
$issuedAt = time();
$expirationTime = $issuedAt + (15 * 60); // Yenə 15 dəqiqə
$payload = [
'iat' => $issuedAt,
'exp' => $expirationTime,
'sub' => $userId,
];
$newAccessToken = JWT::encode($payload, $this->jwtSecretKey, 'HS256');
// Köhnə refresh token-i ləğv edin (təkrar istifadənin qarşısını almaq üçün)
$this->revokeRefreshToken($refreshToken);
// Yeni refresh token yaradın və onu verilənlər bazasına yazın
$newRefreshToken = bin2hex(random_bytes(32));
$newRefreshTokenExpiry = date('Y-m-d H:i:s', strtotime('+7 days'));
$this->saveRefreshToken($userId, $newRefreshToken