بک اند

احراز هویت امن با JWT

JWT به‌خاطر سادگی محبوب شده، اما همین سادگی باعث می‌شود پیاده‌سازی ناامن آن بسیار رایج باشد. این مقاله نشان می‌دهد احراز هویت با JWT را چطور طراحی کنیم که هم راحت باشد و هم در برابر خطاهای رایج مقاوم بماند.

  • نویسنده تیم تتا
  • ۸ دقیقه مطالعه
  • به‌روزرسانی:
احراز هویت امن با JWT

JWT چطور کار می‌کند؟

پس از ورود موفق، سرور یک توکن امضاشده می‌سازد و به کلاینت می‌دهد. کلاینت آن را در هدر Authorization: Bearer … هر درخواست می‌فرستد و سرور فقط با بررسی امضا، هویت را می‌شناسد، بدون اینکه به پایگاه‌داده یا حافظه‌ی نشست سر بزند. این بدون‌حالت‌بودن، JWT را برای APIهای مقیاس‌پذیر جذاب می‌کند.

یک JWT سه بخش جداشده با نقطه دارد: هدر (الگوریتم)، بدنه (claimها) و امضا. بخش‌های اول و دوم فقط Base64URL هستند و هر کسی می‌تواند بخواندشان؛ امضا فقط جلوی دستکاری را می‌گیرد.

json
// payload of a typical access token
{
  "sub": "user_42",          // who
  "iss": "https://api.example.com",
  "aud": "example-web",
  "iat": 1795000000,
  "exp": 1795000900,         // 15 minutes later
  "role": "editor"
}

Access token و refresh token

اگر توکن دزدیده شود، تا زمان انقضا معتبر است و سرور بدون‌حالت نمی‌تواند آن را «لغو» کند. بنابراین از دو توکن استفاده می‌کنیم:

مقایسه‌ی دو نوع توکن
ویژگیAccess tokenRefresh token
عمر۵ تا ۱۵ دقیقهچند روز تا چند هفته
کاربردفراخوانی APIهافقط دریافت access token جدید
ذخیره در سرورنداردهش آن در پایگاه‌داده ذخیره می‌شود
ابطالبا انقضای کوتاهحذف رکورد از پایگاه‌داده

بهتر است refresh token چرخشی (rotating) باشد: با هر استفاده، توکن جدید صادر و قبلی باطل می‌شود. اگر توکن قدیمی دوباره ظاهر شد، یعنی کسی آن را کپی کرده؛ کل خانواده‌ی توکن‌های آن نشست را باطل کنید. چون ابطال، رکورد سمت سرور می‌خواهد، می‌توانید فهرست‌های سریع را در Redis نگه دارید.

تصویر انتزاعی و رنگارنگ از یک کلید دیجیتال درخشان و دو توکن که میان کاربر و سرور رد و بدل می‌شوند
توکن کوتاه‌عمر خسارت سرقت را محدود می‌کند و refresh چرخشی، سرقت را قابل‌تشخیص می‌کند.

صدور و اعتبارسنجی درست

نمونه‌ای با کتابخانه‌ی jsonwebtoken در Node.js. نکته‌ی کلیدی در اعتبارسنجی، تعیین صریح الگوریتم و issuer/audience است:

js
const jwt = require("jsonwebtoken");

function issue(user) {
  return jwt.sign({ sub: user.id, role: user.role }, process.env.JWT_SECRET, {
    algorithm: "HS256", expiresIn: "15m",
    issuer: "https://api.example.com", audience: "example-web"
  });
}

function verify(token) {
  // throws on bad signature, expiry, wrong iss/aud or other algorithm
  return jwt.verify(token, process.env.JWT_SECRET, {
    algorithms: ["HS256"],
    issuer: "https://api.example.com", audience: "example-web"
  });
}
  • کلید را در متغیر محیطی نگه دارید و حداقل ۳۲ بایت تصادفی باشد؛ هرگز در مخزن کد نگذارید.
  • اگر چند سرویس توکن را بررسی می‌کنند، به‌جای کلید مشترک از امضای نامتقارن (RS256 یا ES256) استفاده کنید تا فقط یک سرویس بتواند توکن بسازد.
  • رمزها را با argon2 یا bcrypt هش کنید، نه SHA ساده.

توکن را کجا نگه داریم؟

در مرورگر دو گزینه‌ی اصلی دارید و هر دو معاوضه دارند:

  • localStorage: پیاده‌سازی ساده، اما هر اسکریپتی که روی صفحه اجرا شود (مثلاً با یک حفره‌ی XSS یا وابستگی آلوده) می‌تواند توکن را بخواند.
  • کوکی HttpOnly + Secure + SameSite: جاوااسکریپت به آن دسترسی ندارد، پس سرقت با XSS دشوارتر است؛ در عوض باید در برابر CSRF با SameSite=Lax یا Strict و توکن CSRF محافظت کنید.
js
res.cookie("refresh_token", token, {
  httpOnly: true, secure: true, sameSite: "strict",
  path: "/auth/refresh", maxAge: 7 * 24 * 3600 * 1000
});

الگوی عملی: access token را در حافظه‌ی برنامه نگه دارید و refresh token را در کوکی HttpOnly که فقط به مسیر /auth/refresh ارسال می‌شود. در موبایل از Keychain یا Keystore استفاده کنید.

خطاهای رایج که امنیت را از بین می‌برند

  • پذیرفتن alg: none یا الگوریتم دلخواه توکن. الگوریتم را سمت سرور ثابت کنید و از پارامتر algorithms استفاده کنید.
  • رمز ضعیف برای HS256. کلیدهای کوتاه با حمله‌ی brute-force شکسته می‌شوند.
  • عمر طولانی access token. «۳۰ روز» یعنی یک ماه فرصت برای مهاجم.
  • نادیده‌گرفتن exp و aud. توکن یک سرویس نباید در سرویس دیگر پذیرفته شود.
  • اعتماد به claim نقش بدون بررسی. برای عملیات حساس، دسترسی را از پایگاه‌داده هم تأیید کنید.
  • لاگ‌کردن توکن کامل. توکن در لاگ یعنی لو رفتن آن به هرکس که لاگ را می‌بیند.

فهرست جامع‌تر خطرهای API را در OWASP API Security Project ببینید و جزئیات استاندارد را در راهنمای احراز هویت HTTP در MDN بخوانید.

آیا اصلاً به JWT نیاز دارید؟

برای یک سایت یکپارچه‌ی ساده که فقط یک سرور دارد، نشست سمت سرور با کوکی (session) ساده‌تر و امن‌تر است و ابطالش لحظه‌ای است. JWT زمانی می‌ارزد که چند سرویس مستقل یا اپلیکیشن موبایل و وب با یک API کار کنند و نخواهید هر درخواست به مخزن نشست مشترک برسد.

اگر معماری شما به سمت سرویس‌های جدا می‌رود، مقاله‌ی معماری میکروسرویس‌ها نشان می‌دهد توکن‌ها در این مرزها چه نقشی دارند. هر احراز هویتی هم باید با تست پوشش داده شود؛ نمونه‌ی آن در تست خودکار در لاراول آمده است. و اگر سامانه‌ی ورود یا API امن برای محصولتان می‌خواهید، با ما تماس بگیرید.

پرسش‌های متداول

JWT امن است؟

خود فرمت امن است، به شرط پیاده‌سازی درست: الگوریتم ثابت، کلید قوی، عمر کوتاه، اعتبارسنجی exp و aud و ذخیره‌سازی محتاطانه. بیشتر حفره‌ها از پیاده‌سازی می‌آیند نه از استاندارد.

JWT را کجا ذخیره کنم؟

برای وب، کوکی HttpOnly با Secure و SameSite معمولاً بهتر از localStorage است، چون جاوااسکریپت صفحه نمی‌تواند آن را بخواند. در موبایل از Keychain در iOS و Keystore در اندروید استفاده کنید.

چطور JWT را باطل کنم؟

access token کوتاه‌عمر را به انقضا بسپارید و refresh token را در پایگاه‌داده نگه دارید تا با حذف رکورد باطل شود. برای ابطال فوری می‌توانید شناسه‌ی توکن (jti) را در فهرست سیاه Redis بگذارید.

تفاوت JWT و session چیست؟

در session داده‌ی نشست سمت سرور است و کلاینت فقط شناسه دارد؛ در JWT اطلاعات داخل توکن امضاشده است. session ابطال ساده‌تری دارد و JWT بدون‌حالت‌تر است.

دیدگاه‌ها

    هنوز دیدگاهی ثبت نشده است. اولین نفر باشید.

    دیدگاه‌ها پس از بررسی منتشر می‌شوند.