OAuth و OIDC چه میکنند؟
OAuth 2.0 یک چارچوب تفویض دسترسی است: به برنامهی «الف» اجازه میدهد بدون دانستن رمز عبور کاربر، به بخشی از دادهی او در سرویس «ب» دسترسی پیدا کند. OpenID Connect (OIDC) لایهای است که روی OAuth 2.0 ساخته شده و آن را از تفویض دسترسی به احراز هویت گسترش میدهد؛ خروجی مشخصهی آن id_token است، یک JWT که میگوید کاربر کیست.
تفکیک این دو مهم است: «ورود با گوگل» یک عملیات OIDC است، نه صرفاً OAuth. استفاده از Access Token بهجای ID Token برای تشخیص هویت کاربر، یک الگوی ناامن شناختهشده است.
جریان اصلی و توصیهشده، Authorization Code است:
- کلاینت کاربر را به سرور مجوزدهی میفرستد، با
client_id،redirect_uri،scope،stateوcode_challenge. - کاربر احراز هویت میشود و رضایت میدهد.
- سرور مجوزدهی کاربر را به
redirect_uriبازمیگرداند، همراه با یک کد مجوز کوتاهعمر. - کلاینت در یک درخواست پشتصحنه (Back-channel) کد را به همراه
code_verifierبا توکن دسترسی مبادله میکند.
ارزش این طراحی در گام چهارم است: توکن هرگز از مرورگر عبور نمیکند. هر انحرافی از این الگو — مثل جریان Implicit که توکن را در قطعهی URL برمیگرداند — این ویژگی را از بین میبرد.
وضعیت واقعی OAuth 2.1
«OAuth 2.1 استاندارد منتشرشده است و باید به آن مهاجرت کنیم.» OAuth 2.1 هنوز RFC نیست. این سند یک Internet-Draft فعال گروه کاری IETF است؛ نسخهی جاری draft-ietf-oauth-v2-1-15 با تاریخ ۲ مارس ۲۰۲۶ است و هدفگذاری ارسال به IESG برای دسامبر ۲۰۲۶ اعلام شده. سایت رسمی oauth.net هم آن را «تلاشی در جریان» توصیف میکند. در گزارش تست نفوذ میتوان به آن بهعنوان راهنمای عملی جاری ارجاع داد، اما نه بهعنوان استاندارد منتشرشده.
مهمترین تغییرات پیشنهادی OAuth 2.1 نسبت به 2.0 که در عمل باید رعایت شوند:
- PKCE برای همهی کلاینتهایی که از جریان Authorization Code استفاده میکنند الزامی است — نه فقط کلاینتهای عمومی و موبایل.
- مقایسهی
redirect_uriباید تطبیق رشتهای دقیق باشد؛ درخواستهایی با آدرسی که دقیقاً با آدرس ثبتشده یکی نیست باید رد شوند (با استثنائات محدود برای Loopback). - جریان Implicit حذف شده است — RFC 9700 (سند Security BCP برای OAuth 2.0) آن را ناامن میداند.
- جریان Resource Owner Password Credentials حذف شده است.
- توکن Bearer نباید در Query String آدرس منتقل شود.
- Refresh Token برای کلاینتهای عمومی باید یا مقید به فرستنده باشد یا چرخشی.
PKCE و state: دو کنترل متفاوت که جای هم را نمیگیرند
این یکی از پرتکرارترین سوءتفاهمها در بررسی معماری است:
| کنترل | چه چیزی را متوقف میکند |
|---|---|
| PKCE (RFC 7636) | سرقت و تزریق کد مجوز. کلاینت یک code_verifier تصادفی میسازد و هش آن را در درخواست اولیه میفرستد؛ در گام تبادل، اصل مقدار را ارائه میدهد. کدِ دزدیدهشده بدون code_verifier بیارزش است. |
state | CSRF روی خود جریان مجوزدهی. بدون آن، مهاجم میتواند پاسخ مجوزدهی حساب خودش را به مرورگر قربانی تحمیل کند و حساب قربانی را به حساب مهاجم پیوند بزند (Account Linking Takeover). |
یعنی PKCE جایگزین state نیست و هر دو لازماند. در OIDC یک کنترل سوم هم اضافه میشود: nonce، که جلوی بازپخش id_token را میگیرد و باید در سمت کلاینت با مقدار ارسالی مقایسه شود.
حالتهای تنزل هم باید آزموده شوند: آیا سرور درخواستی بدون code_challenge را میپذیرد؟ آیا code_challenge_method=plain را قبول میکند؟ آیا state را واقعاً بررسی میکند یا فقط بازتاب میدهد؟
خطاهای پیکربندی رایج
- اعتبارسنجی سهلگیرانهی
redirect_uri. کلاسیکترین مسیر به سرقت کد مجوز. الگوهای معیوب: تطبیق پیشوندی، پذیرش زیردامنه با Wildcard، اجازهی افزودن Query یا Fragment دلخواه، و پذیرش/../در مسیر. - زنجیرهی Open Redirect. اگر یکی از آدرسهای مجاز خودش یک تغییر مسیر باز داشته باشد، مهاجم کد را از طریق همان آدرس مجاز به دامنهی خودش منتقل میکند. این تنها دلیلی است که برای رد کردن «Open Redirect کماهمیت است» کافی است.
- نشت کد از طریق
Referer. اگر صفحهی مقصد اسکریپت ثالث بارگذاری کند، کد مجوز در هدرRefererبه بیرون میرود. همچنین آدرس کامل در تاریخچهی مرورگر و لاگهای پروکسی میماند. - نبود اعتبارسنجی
id_tokenدر OIDC: بررسی نشدن امضا، نبود بررسیiss/aud/exp/nonce، و پذیرشalg: none. - سردرگمی مخاطب توکن: توکنی که برای یک منبع صادر شده، توسط منبع دیگری پذیرفته میشود.
- ارتقای دامنهی دسترسی (Scope): سرور
scopeدرخواستی را نادیده میگیرد، یا کلاینت دامنهای بهمراتب گستردهتر از نیازش میخواهد. - استفاده از جریان Implicit — در ۲۰۲۶ یک یافتهی خودکار است.
- ثبتنام پویای کلاینت (Dynamic Client Registration) که باز رها شده و به مهاجم اجازه میدهد کلاینت خودش را با
redirect_uriدلخواه ثبت کند.
سناریوی واقعی
یک سرویس با «ورود با حساب سازمانی» که بهعنوان کلاینت محرمانه در سرور مجوزدهی ثبت شده است. تیم توسعه چون کلاینت را محرمانه میداند، PKCE را پیاده نکرده و اعتبارسنجی redirect_uri را با تطبیق پیشوندی انجام داده تا محیطهای Staging هم کار کنند.
در تست، مقدار redirect_uri=https://app.example.com/cb/../../../redirect?to= پذیرفته میشود، چون رشته با پیشوند ثبتشده شروع میشود. مسیر /redirect روی همان دامنه یک تغییر مسیر باز دارد. نتیجه: کد مجوز به دامنهی خارج از کنترل کلاینت منتقل میشود و چون PKCE وجود ندارد، مبادلهی کد با توکن دسترسی بدون هیچ راز اضافهای ممکن است.
هر سه ضعف بهتنهایی «متوسط» بهنظر میرسیدند: تطبیق پیشوندی، تغییر مسیر باز، و نبود PKCE. زنجیرهی آنها تصاحب حساب است. این دقیقاً دلیلی است که یافتههای OAuth باید بهصورت زنجیرهای ارزیابی شوند، نه تکتک.
چگونه در تست نفوذ کشف میشود
- ثبت کامل جریان در پروکسی: از کلیک روی «ورود با …» تا بازگشت نهایی. هر پارامتر درخواست مجوزدهی باید مستند شود.
- خواندن سند کشف: مسیر
/.well-known/openid-configurationفهرست اندپوینتها، الگوریتمهای پشتیبانیشده و قابلیتهای سرور را میدهد — از جمله اینکه آیاplainبرای PKCE مجاز است. - آزمون
redirect_uri: افزودن مسیر، افزودن Query، تغییر زیردامنه، تغییر پورت، تغییر طرح بهhttp، افزودن@و کاراکترهای کدشده. هر پذیرشی که به دامنهی خارج از کنترل کلاینت ختم شود، یافته است. - حذف کنترلها:
stateرا حذف کنید و ببینید جریان کامل میشود یا نه؛ همین را برایcode_challengeوnonceتکرار کنید. - بازاستفاده و عمر کد: یک کد مجوز را دو بار مبادله کنید. کد باید یکبارمصرف باشد و بازاستفاده باید کل توکنهای صادرشده را باطل کند.
- آزمون
id_token: همهی حملات JWT روی آن قابل اجراست.
این آزمونها بخش استانداردی از تست نفوذ برنامهی وب هستند و با اسکنر خودکار پوشش داده نمیشوند، چون ابزار نمیداند کدام redirect_uri مجاز است.
نمونهی فنی و نگاشت به استانداردها
یک درخواست مجوزدهی درست، همهی کنترلها را با هم دارد:
GET /authorize?response_type=code
&client_id=app123
&redirect_uri=https://app.example.com/cb
&scope=openid%20profile
&state=<random>&nonce=<random>
&code_challenge=<S256-hash>
&code_challenge_method=S256نبود هر یک از state، nonce یا code_challenge — و نیز مقدار plain برای روش چالش — در گزارش بهعنوان یافته ثبت میشود.
نگاشت به استانداردها
- A07:2025 — Authentication Failures برای نقصهای احراز هویت و نشست؛ A01:2025 — Broken Access Control وقتی نتیجه، دسترسی به دادهی کاربر دیگر باشد؛ A02:2025 — Security Misconfiguration برای پیکربندی سهلگیرانهی سرور مجوزدهی.
- اسناد مرجع: RFC 6749 (OAuth 2.0)، RFC 7636 (PKCE)، RFC 9700 (Security BCP)، و پیشنویس draft-ietf-oauth-v2-1. برای برنامههای تکصفحهای، پیشنویس
draft-ietf-oauth-browser-based-appsمرجع اختصاصی است.
پرسشهای متداول
آیا OAuth 2.1 منتشر شده است؟
خیر. OAuth 2.1 هنوز یک Internet-Draft فعال گروه کاری IETF است؛ نسخهی جاری draft-ietf-oauth-v2-1-15 با تاریخ ۲ مارس ۲۰۲۶ است و هنوز وارد فرایند IESG نشده. توصیههای آن ارزش عملی دارند، اما نباید آن را «استاندارد منتشرشده» خطاب کرد.
آیا PKCE فقط برای اپلیکیشن موبایل لازم است؟
خیر. این یک باور قدیمی است. در OAuth 2.1 استفاده از PKCE برای همهی کلاینتهایی که از جریان Authorization Code استفاده میکنند الزامی است — از جمله کلاینتهای محرمانهی سمت سرور. دلیلش این است که PKCE مسیر تزریق و سرقت کد مجوز را میبندد، و این مسیر مختص موبایل نیست.
اگر PKCE داریم، آیا هنوز به state نیاز است؟
بله. این دو مسئلهی متفاوتی را حل میکنند: PKCE از تزریق و سرقت کد جلوگیری میکند، و state از CSRF روی خود جریان مجوزدهی — یعنی حملهای که حساب قربانی را به حساب مهاجم پیوند میزند. هر دو لازماند و هیچکدام جای دیگری را نمیگیرد.
چرا جریان Implicit دیگر توصیه نمیشود؟
چون توکن دسترسی را در قطعهی URL به مرورگر برمیگرداند؛ در نتیجه توکن در تاریخچهی مرورگر، لاگها و هدر Referer قابل نشت است و امکان مقیدسازی آن به کلاینت وجود ندارد. RFC 9700 آن را ناامن میداند و پیشنویس OAuth 2.1 حذفش کرده است. جایگزین درست، Authorization Code بههمراه PKCE است.