JWT (JSON Web Token) ساده و پرکاربرد است و همین سادگی باعث میشود در عمل زیاد اشتباه پیادهسازی شود. این راهنما ساختار توکن را توضیح میدهد، فهرست درست اعتبارسنجی را ارائه میکند و هشت اشتباه امنیتی را که در بازبینیها بیش از بقیه دیده میشود بررسی میکند.
ساختار یک JWT
یک JWT سه بخش دارد که با نقطه از هم جدا میشوند: header.payload.signature. دو بخش اول JSON هستند که با Base64URL کدگذاری شدهاند (نه رمزنگاری)؛ بخش سوم امضای دیجیتال دو بخش اول است.
// Header
{ "alg": "RS256", "typ": "JWT", "kid": "2026-10" }
// Payload (claims)
{
"iss": "https://auth.example.com",
"sub": "user_421",
"aud": "api.example.com",
"iat": 1760000000,
"exp": 1760000900,
"scope": "orders:read"
}
JWTJWT Decoderتوکن خودتان را بچسبانید و Header، Payload، انقضا و امضا را ببینید؛ همهچیز داخل مرورگر انجام میشود.
ادعاهای (claims) استاندارد
| claim | معنی |
|---|---|
| iss | صادرکننده؛ چه کسی توکن را ساخته است |
| sub | موضوع؛ معمولاً شناسهی کاربر |
| aud | مخاطب؛ این توکن برای کدام سرویس است |
| exp | زمان انقضا (Unix timestamp به ثانیه) |
| nbf | پیش از این زمان معتبر نیست |
| iat | زمان صدور |
| jti | شناسهی یکتای توکن؛ برای ابطال و جلوگیری از بازپخش |
فیلدهای زمانی عدد Unix هستند؛ برای دیدن تاریخ خوانا از مبدل Timestamp استفاده کنید.
HS256 در برابر RS256 و ES256
HS256 الگوریتم متقارن است: یک کلید مخفی هم برای امضا و هم برای بررسی. ساده است و برای یک سرویس که هم توکن میسازد و هم بررسی میکند مناسب. مشکل وقتی پیش میآید که چند سرویس باید توکن را بررسی کنند: همهی آنها کلیدی دارند که میتواند توکن جعل کند.
RS256 (RSA) و ES256 (منحنی بیضوی) نامتقارناند: فقط سرویس احراز هویت کلید خصوصی دارد و بقیه با کلید عمومی فقط میتوانند بررسی کنند. ES256 امضاهای کوچکتر و بررسی سریعتری دارد. انتشار کلیدهای عمومی معمولاً از طریق یک آدرس JWKS انجام میشود و هدر kid مشخص میکند کدام کلید مناسب است، که چرخش کلید (key rotation) را بدون قطعی ممکن میکند.
فهرست درست اعتبارسنجی
پیش از اینکه به هر claim اعتماد کنید، بهترتیب این کارها را انجام دهید:
- الگوریتم را خودتان تعیین کنید، نه توکن. فهرست الگوریتمهای مجاز را در کد بنویسید.
- امضا را با کلید درست بررسی کنید (انتخاب کلید از روی
kidفقط از میان کلیدهای مورد اعتماد خودتان). - iss و aud را مقایسه کنید با مقدار مورد انتظار؛ توکنی که برای سرویس دیگری صادر شده نباید اینجا پذیرفته شود.
- exp و nbf را بررسی کنید، با اختلاف زمانی (leeway) کوچک (مثلاً ۳۰ تا ۶۰ ثانیه) برای ساعتهای ناهماهنگ.
- در صورت نیاز، jti را در فهرست ابطالشدهها جستوجو کنید.
// Node.js — کتابخانهی jsonwebtoken
const payload = jwt.verify(token, publicKey, {
algorithms: ['RS256'], // فهرست مجاز، نه چیزی که توکن میگوید
issuer: 'https://auth.example.com',
audience: 'api.example.com',
clockTolerance: 30, // ثانیه
});
۸ اشتباه امنیتی رایج
۱. پذیرفتن alg: none
توکن با "alg": "none" یعنی «امضا ندارم». کتابخانههای قدیمی یا پیکربندیهای اشتباه آن را معتبر میشمردند، که یعنی هر کسی میتوانست توکن دلخواه بسازد. همیشه فهرست مجاز الگوریتمها را صریح بدهید و none را هرگز در آن نگذارید.
۲. حملهی algorithm confusion
اگر سرور الگوریتم را از هدر توکن بخواند، مهاجم میتواند توکنی با alg: HS256 بسازد و آن را با کلید عمومی سرور (که همه میدانند) امضا کند؛ اگر سرور همان کلید عمومی را بهعنوان «کلید مخفی HMAC» به کار ببرد، امضا معتبر درمیآید. راهحل همان قاعدهی قبلی است: الگوریتم را سمت سرور قفل کنید.
۳. کلید مخفی ضعیف در HS256
امضای HS256 را میتوان آفلاین حدس زد (brute-force)؛ اگر کلید کوتاه یا قابلحدس مثل secret باشد، در چند ثانیه شکسته میشود. کلید باید رشتهای تصادفی دستکم ۳۲ بایتی باشد.
openssl rand -base64 48
۴. گذاشتن دادهی حساس در Payload
Payload فقط کدگذاری شده است. رمز، شمارهی کارت یا اطلاعات شخصی غیرضروری را داخل توکن نگذارید؛ حتی اگر توکن فقط بین سرورهای شما میچرخد، در لاگها و پراکسیها دیده میشود. میتوانید خودتان امتحان کنید: هر توکن را در ابزار Base64 بچسبانید، و محتوایش خوانده میشود.
۵. عمر طولانی و نبود راه ابطال
توکنی که یک سال معتبر است، یعنی توکن دزدیدهشده یک سال کار میکند. الگوی رایج: access token با عمر کوتاه (۵ تا ۱۵ دقیقه) + refresh token قابلابطال که در سرور ثبت میشود.
۶. نگهداری توکن در localStorage
هر اسکریپتی که روی صفحه اجرا شود (یک XSS یا یک وابستگی آلوده) میتواند localStorage را بخواند. کوکی با HttpOnly از دسترس جاوااسکریپت خارج است؛ با Secure و SameSite=Lax یا Strict ترکیبش کنید و برای درخواستهای تغییر وضعیت، حفاظت CSRF را فراموش نکنید.
۷. بررسی نکردن iss و aud
اگر چند سرویس از یک سرور احراز هویت استفاده میکنند، توکنِ سرویس A میتواند به سرویس B هم ارائه شود؛ مگر اینکه aud مقایسه شود. این یک خطای «فراموششده» است، نه حملهی پیچیده.
۸. استفاده از JWT برای همهچیز
اگر اپلیکیشن شما یک سرور و یک پایگاهداده دارد، سشن سمتسرور سادهتر، قابلابطالتر و امنتر از JWT است. JWT زمانی میارزد که واقعاً چند سرویس مستقل و بدون وضعیت مشترک دارید.
عیبیابی عملی توکن
وقتی API پاسخ 401 میدهد، چند سؤال بهترتیب جواب میدهند چرا:
- توکن منقضی شده؟ (
expرا با زمان فعلی مقایسه کنید.) - ساعت سرور و کلاینت ناهماهنگ است؟ (
nbfیاiatدر آینده است.) audیاissبا تنظیمات API یکی است؟- الگوریتم و
kidهمان چیزی است که سرور انتظار دارد؟ - توکن در هدر
Authorization: Bearer …بدون کاراکتر اضافی (مثل خط جدید) ارسال شده؟
جمعبندی
امنیت JWT از امضا و اعتبارسنجی درست میآید، نه از پنهانبودن محتوا: الگوریتم را سمت سرور قفل کنید، iss، aud و exp را بررسی کنید، عمر توکن را کوتاه نگه دارید و برای نگهداری آن در مرورگر از کوکی HttpOnly استفاده کنید. اگر API شما پشت Nginx است، راهنمای پیکربندی reverse proxy نحوهی عبور درست هدر Authorization و X-Forwarded-Proto را نشان میدهد.