ورود با گوگل، در نگاه کاربر یک دکمه ساده است ولی در پشت صحنه یکی از پرجزئیات‌ترین جریان‌های احراز هویت مدرن را اجرا می‌کند. من در پروژه‌های واقعی زیاد دیده‌ام که تیم توسعه، جریان OAuth را در قالب یک کتابخانه نصب می‌کند، دکمه را روی صفحه می‌گذارد و فکر می‌کند کار تمام است؛ درحالی‌که در پس‌صحنه، جزئیات مهمی مثل PKCE، بررسی State، و اعتبارسنجی ID Token نادیده گرفته شده‌اند. یکی از تجربه‌های تلخ من در این حوزه، پروژه‌ای بود که در آن به‌دلیل تنظیم نادرست Redirect URI، هر مهاجم می‌توانست با یک دامنه دست‌کاری‌شده، حساب کاربران را در اختیار بگیرد.

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

OAuth چیست و چه مسئله‌ای را حل می‌کند؟

OAuth که مخفف Open Authorization است و در مرجع فنی وب با همین نام OAuth شناخته می‌شود، یک استاندارد باز (RFC 6749) برای اعطای دسترسی محدود به منابع یک سرویس، بدون افشای رمز عبور به سرویس دیگر است. برای درک بهتر، یک مثال عینی: فرض کنید یک اپلیکیشن تقویم ساخته‌اید که می‌خواهد رویدادهای گوگل کاربر را بخواند. بدون OAuth، کاربر باید رمز عبور گوگل خود را به اپلیکیشن شما می‌داد — یک فاجعه امنیتی. با OAuth، کاربر به گوگل هدایت می‌شود، آنجا به شما اجازه دسترسی می‌دهد، و گوگل یک توکن محدود به شما می‌دهد که فقط به تقویم دسترسی دارد، نه به ایمیل یا سایر سرویس‌های گوگل.

مسئله‌ای که OAuth حل می‌کند، در ادبیات امنیتی با نام «مشکل رمز عبور سوم» شناخته می‌شود: چطور یک سرویس را به منبع دیگری دسترسی بدهیم بدون اینکه اعتبارنامه کاربر را در اختیار آن سرویس قرار دهیم. این مسئله در دهه‌های گذشته با راه‌حل‌های مختلفی تلاش شده بود، ولی OAuth 1.0 در سال ۲۰۰۷ اولین راه‌حل استاندارد و مقیاس‌پذیری بود که این مسئله را به‌طور رضایت‌بخش حل کرد. OAuth 2.0 در سال ۲۰۱۲ منتشر شد و با بهبودهای قابل توجه در سادگی و انعطاف‌پذیری، امروز استاندارد غالب است.

OAuth یک پروتکل مجوز است، نه احراز هویت

یکی از رایج‌ترین سوءتفاهم‌ها در پروژه‌های واقعی این است که OAuth را به‌عنوان یک پروتکل احراز هویت در نظر بگیرند. OAuth در واقع یک پروتکل «مجوز» (Authorization) است: به یک سرویس اجازه دسترسی به منبع سرویس دیگر را می‌دهد، ولی درباره هویت کاربر حرفی نمی‌زند. لایه هویت، توسط OpenID Connect اضافه می‌شود که در ادامه به آن می‌پردازم. اگر با تفاوت این دو مفهوم آشنایی کمتری دارید، تفاوت احراز هویت و مجوزدهی چیست؟ تفکیک دقیق را توضیح می‌دهد.

این تفکیک در پیاده‌سازی‌های واقعی اهمیت مستقیم دارد. اگر فقط از OAuth استفاده کنید، پس از دریافت توکن، شما فقط می‌دانید این توکن به یک منبع دسترسی دارد، ولی نمی‌دانید صاحب آن کیست. برای ورود کاربر، باید OpenID Connect را هم فعال کنید یا یک درخواست جداگانه به endpoint اطلاعات کاربر بزنید.

OAuth می‌گوید «این اپلیکیشن اجازه دارد به این منبع دسترسی داشته باشد»؛ نمی‌گوید «این کاربر کیست». این تفکیک، مرز بین یک پیاده‌سازی حرفه‌ای و یک پیاده‌سازی ساده‌انگارانه است.

چرا ورود با گوگل یک انقلاب کوچک در احراز هویت بود؟

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

ورود با گوگل که در سال ۲۰۱۲ بر پایه OAuth 2.0 و OpenID Connect معرفی شد، این سه مشکل را به‌طور همزمان حل کرد. کاربر در سه ثانیه وارد سایت می‌شود، رمز جدیدی به‌خاطر نمی‌سپارد، و سطح امنیت بالا می‌رود چون رمز حساب گوگل، امن‌تر از رمزهای معمولی است. من در پروژه‌های واقعی دیده‌ام که اضافه کردن ورود با گوگل، نرخ ثبت‌نام در بعضی سایت‌ها را تا دو برابر افزایش می‌دهد.

ولی این مزیت، هزینه‌ای هم دارد که در پروژه‌های واقعی باید آن را سنجید. اولین هزینه، وابستگی به سرویس خارجی است: اگر گوگل به‌دلیل تحریم یا محدودیت جغرافیایی دسترسی را قطع کند، ورود کاربران شما هم قطع می‌شود. این چالش در پروژه‌های ایرانی واقعیت روزمره است. دومین هزینه، مسئله حریم خصوصی است: بعضی کاربران تمایلی ندارند که هویت‌شان با حساب گوگل گره بخورد. راه‌حل، ارائه ورود با گوگل به‌عنوان یکی از چند گزینه است، نه تنها گزینه.

مدل‌های ورود با سرویس خارجی

در عمل، سه مدل اصلی برای ورود با سرویس خارجی وجود دارد که هر کدام سناریوی خودش را دارد. مدل اول، ورود با یک سرویس بزرگ (گوگل، مایکروسافت، اپل) که برای سایت‌های عمومی انتخاب خوبی است. مدل دوم، ورود با گیت‌هاب که در پروژه‌های توسعه‌دهنده رایج است چون مخاطب‌شان کاربران فنی هستند. مدل سوم، ورود سازمانی با SSO که در پروژه‌های سازمانی استفاده می‌شود و در SSO چطور تجربه کاربری سازمانی را متحول می‌کند؟ مفصلاً به آن پرداخته‌ام.

آناتومی OAuth 2.0: چهار نقش اصلی

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

نقش اول: Resource Owner

Resource Owner یا «مالک منبع» همان کاربری است که صاحب داده‌ها است. وقتی کاربر روی دکمه «ورود با گوگل» کلیک می‌کند، نقش Resource Owner را ایفا می‌کند چون تصمیم می‌گیرد به اپلیکیشن شما اجازه دسترسی به اطلاعات گوگلش را بدهد یا نه.

نقش دوم: Client

Client یا «مشتری»، همان اپلیکیشن شماست که می‌خواهد به منبع دسترسی پیدا کند. Client در اصطلاح OAuth، به دو نوع تقسیم می‌شود. نوع اول، Confidential Client است که می‌تواند Client Secret را محرمانه نگه دارد — مثل یک سایت سرور-محور. نوع دوم، Public Client است که نمی‌تواند Secret را محرمانه نگه دارد — مثل یک اپلیکیشن موبایل یا SPA. این تفکیک، در انتخاب جریان OAuth اثر مستقیم دارد و در بخش پیاده‌سازی به آن برمی‌گردم.

نقش سوم: Authorization Server

Authorization Server یا «سرور مجوز»، همان سرویس گوگل است که تصمیم می‌گیرد چه دسترسی‌ای به Client بدهد. این سرور، دو endpoint مهم دارد: Authorization Endpoint که برای درخواست اولیه استفاده می‌شود، و Token Endpoint که برای تبدیل کد به توکن استفاده می‌شود. مستندات دقیق این endpointها در مستندات رسمی Google OAuth 2.0 قابل مطالعه است.

نقش چهارم: Resource Server

Resource Server یا «سرور منبع»، سروری است که داده‌ها را نگه می‌دارد و در ازای توکن معتبر، آن‌ها را برمی‌گرداند. در ورود با گوگل، این سرور همان API گوگل است که اطلاعات کاربر را در ازای توکن ارائه می‌دهد. توجه کنید که در خیلی از پیاده‌سازی‌ها، Authorization Server و Resource Server در یک زیرساخت ادغام شده‌اند، ولی مفهوماً متفاوتند.

نقشمسئولیتمثال در ورود با گوگل
Resource Ownerصاحب دادهکاربری که روی دکمه می‌زند
Clientاپلیکیشن درخواست‌کنندهسایت شما
Authorization Serverصدور توکنaccounts.google.com
Resource Serverنگهدارنده دادهgoogleapis.com

جریان Authorization Code با PKCE گام‌به‌گام

حالا که نقش‌ها را می‌شناسید، جریان دقیق ورود با گوگل را گام‌به‌گام باز می‌کنیم. جریان غالب در ورود با گوگل، Authorization Code Flow است که در آن، Client یک کد موقت می‌گیرد و بعد آن کد را با یک درخواست سرور-به-سرور به توکن تبدیل می‌کند. در سال‌های اخیر، PKCE هم به این جریان اضافه شده که امنیت را به‌طور محسوس بالا می‌برد.

گام اول: درخواست مجوز

اولین گام، هدایت کاربر به Authorization Endpoint گوگل است. یک نمونه URL:

https://accounts.google.com/o/oauth2/v2/auth?
  client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yoursite.com/oauth/callback
  &response_type=code
  &scope=openid%20email%20profile
  &state=RANDOM_STATE_VALUE
  &code_challenge=PKCE_CHALLENGE
  &code_challenge_method=S256

سه پارامتر کلیدی در این URL وجود دارد. پارامتر state یک رشته تصادفی است که برای جلوگیری از حمله CSRF استفاده می‌شود؛ سرور شما باید این مقدار را ذخیره کند و هنگام بازگشت کاربر، بررسی کند که مقدار یکسان باشد. پارامتر code_challenge که بخشی از PKCE است، امنیت جریان را در محیط‌های غیرمحرمانه تضمین می‌کند. پارامتر scope تعیین می‌کند که به چه اطلاعاتی نیاز دارید.

گام دوم: رضایت کاربر

کاربر به صفحه گوگل هدایت می‌شود و آنجا فهرست دسترسی‌هایی که اپلیکیشن شما درخواست کرده را می‌بیند. اگر کاربر قبلاً این اپلیکیشن را تأیید کرده باشد، این مرحله ممکن است به‌طور خودکار رد شود. اگر رد کند، به Redirect URI بازمی‌گردد با یک پارامتر خطا.

گام سوم: دریافت Authorization Code

اگر کاربر رضایت بدهد، گوگل کاربر را به Redirect URI مشخص‌شده هدایت می‌کند و یک Authorization Code به‌عنوان پارامتر URL ارسال می‌کند:

https://yoursite.com/oauth/callback?code=AUTHORIZATION_CODE&state=RANDOM_STATE_VALUE

نکته مهم: این Authorization Code فقط یک بار قابل استفاده است و عمر کوتاهی (معمولاً ۱۰ دقیقه) دارد. در گام بعدی، شما این کد را با یک درخواست سرور-به-سرور به توکن تبدیل می‌کنید.

گام چهارم: تبدیل کد به توکن

در این گام، درخواستی از سرور شما به Token Endpoint گوگل ارسال می‌شود:

$response = wp_remote_post( 'https://oauth2.googleapis.com/token', [
    'body' => [
        'code'          => $authorization_code,
        'client_id'     => GOOGLE_CLIENT_ID,
        'client_secret' => GOOGLE_CLIENT_SECRET,
        'redirect_uri'  => 'https://yoursite.com/oauth/callback',
        'grant_type'    => 'authorization_code',
        'code_verifier' => $original_pkce_verifier,
    ],
] );

در پاسخ این درخواست، گوگل یک access_token، یک id_token (اگر scope شامل openid باشد) و در بعضی موارد یک refresh_token برمی‌گرداند. این گام، مرز بین یک پیاده‌سازی امن و یک پیاده‌سازی آسیب‌پذیر است چون در اینجا Client Secret باید محرمانه باشد.

گام پنجم: تأیید ID Token و دریافت اطلاعات کاربر

ID Token یک JWT است که اطلاعات هویتی کاربر را در خود دارد. باید امضای آن با کلید عمومی گوگل بررسی شود، زمان انقضا چک شود، و مقدار aud برابر Client ID شما باشد. بعد از تأیید، می‌توانید اطلاعات کاربر را از Payload توکن بردارید و کاربر را در سایت خودتان ثبت‌نام یا وارد کنید. اگر با مکانیزم دقیق JWT آشنا نیستید، JWT چیست و چه کاربردی در احراز هویت دارد؟ پیش‌نیاز این گام است.

یک نکته مهم در این گام: هرگز به داده‌های ID Token بدون بررسی امضا اعتماد نکنید. من در بازبینی پروژه‌ای به کدی برخورده‌ام که فقط Payload توکن را می‌خواند و کاربر را وارد می‌کرد — یک آسیب‌پذیری جدی که با ساختن یک توکن جعلی به‌سادگی قابل اکسپلویت بود.

در OAuth، اعتماد فقط به چیزی که امضاشده است، مشروع است. هر داده دیگری، حتی اگر به‌نظر معتبر بیاید، فقط یک ادعا است نه یک اثبات.

Client ID، Client Secret و Redirect URI

سه قطعه اطلاعاتی که هنگام ثبت اپلیکیشن در Google Cloud Console دریافت می‌کنید، نقش کلیدی در امنیت جریان دارند. درک دقیق هر کدام، پیش‌نیاز پیاده‌سازی امن است.

Client ID: شناسه عمومی

Client ID یک رشته عمومی است که هویت اپلیکیشن شما را به گوگل معرفی می‌کند. این مقدار، محرمانه نیست و در URL درخواست مجوز ظاهر می‌شود. در تمام درخواست‌ها باید همان Client ID استفاده شود که در Google Cloud Console ثبت کرده‌اید.

Client Secret: کلید محرمانه

Client Secret یک رشته محرمانه است که فقط برای Confidential Client استفاده می‌شود. این مقدار باید در متغیر محیطی یا فایل پیکربندی خارج از مخزن Git نگه داشته شود، نه در کد. من در بازبینی پروژه‌ها بارها دیده‌ام که Client Secret در مخزن عمومی GitHub به‌طور تصادفی کامیت شده — یک فاجعه امنیتی که فوراً باید گزارش و ریست شود.

نکته مهم: در Public Client مثل اپلیکیشن موبایل یا SPA، Client Secret وجود ندارد چون نمی‌تواند محرمانه بماند. در این سناریو، PKCE جای Client Secret را می‌گیرد و امنیت جریان را تضمین می‌کند. اگر روی پروژه‌ای کار می‌کنید که باید در محیط مرورگر Client Secret داشته باشد، یعنی معماری اشتباه را انتخاب کرده‌اید و باید به PKCE مهاجرت کنید.

Redirect URI: بازگشت امن

Redirect URI آدرسی است که گوگل بعد از تأیید کاربر، او را به آنجا بازمی‌گرداند. این پارامتر، مهم‌ترین نقطه امنیتی در OAuth است چون اگر نادرست تنظیم شود، مهاجم می‌تواند کاربران را به دامنه خودش هدایت کند. قواعد امنیتی که در همه پروژه‌ها رعایت می‌کنم:

  • فقط HTTPS: در محیط production، Redirect URI باید حتماً HTTPS باشد. گوگل در بعضی سناریوها HTTP را قبول می‌کند ولی این یک ریسک امنیتی است.
  • بدون Wildcard: هرگز از Wildcard در Redirect URI استفاده نکنید. یک URL دقیق برای هر endpoint ثبت کنید. حتی اگر گوگل اجازه بدهد، این یک آسیب‌پذیری جدی است.
  • مطابقت دقیق: در سمت سرور، قبل از تبدیل کد به توکن، بررسی کنید که Redirect URI درخواست با مقدار ثبت‌شده در Google Cloud Console یکسان است. یک بایت اختلاف، امنیت را به‌طور کامل زیر سؤال می‌برد.
  • محدود به دامنه خودتان: هرگز Redirect URI را به دامنه سرویس خارجی یا دامنه‌ای که در اختیار شما نیست تنظیم نکنید.

نکته تکمیلی درباره امنیت ورود: بعد از اینکه کاربر با گوگل وارد شد، توصیه می‌کنم لایه دوم امنیتی مثل احراز هویت دو مرحله‌ای را هم فعال کنید. اصول کامل این لایه در احراز هویت دو مرحله‌ای چگونه امنیت را افزایش می‌دهد؟ آمده است.

Scope و محدوده دسترسی در ورود با گوگل

Scope در OAuth، محدوده دسترسی را تعیین می‌کند. وقتی درخواست مجوز می‌فرستید، باید صریحاً بگویید به چه اطلاعاتی نیاز دارید. گوگل سه دسته Scope اصلی دارد که در ورود با گوگل پرکاربردند:

  • openid: فعال‌سازی OpenID Connect و دریافت ID Token. این Scope، پایه ورود با گوگل است.
  • email: دسترسی به ایمیل کاربر. برای ثبت‌نام و شناسایی یکتای کاربر لازم است.
  • profile: دسترسی به اطلاعات پروفایل مثل نام، تصویر و زبان. برای نمایش کاربر در پیشخوان سایت شما کاربرد دارد.

اصل طلایی در Scope: حداقل دسترسی. اگر به تقویم کاربر نیازی ندارید، Scope تقویم را درخواست نکنید. درخواست Scope اضافه، اولاً اعتماد کاربر را کم می‌کند چون در صفحه رضایت، فهرست طولانی دسترسی‌ها می‌بیند، و ثانیاً سطح ریسک امنیتی را بالا می‌برد چون اگر توکن لو برود، مهاجم به منابع بیشتری دسترسی دارد.

نکته دیگر: بعضی Scopeها در گوگل نیاز به تأیید تیم امنیتی دارند. اگر Scope شما در دسته‌های حساس مثل Gmail، Drive یا Calendar باشد، اپلیکیشن باید فرآیند تأیید گوگل را طی کند. برای ورود با گوگل معمولاً فقط سه Scope پایه کافی است و نیازی به تأیید نیست. اگر روی پروژه سازمانی کار می‌کنید و چندین سرویس را ادغام می‌کنید، پیاده‌سازی JWT در APIهای مدرن گزینه‌های دیگر را بررسی می‌کند.

Access Token، Refresh Token و چرخه عمرشان

در جریان OAuth، سه نوع توکن مختلف با مسئولیت‌های متفاوت ظاهر می‌شود. درک دقیق این تفکیک، پیش‌نیاز پیاده‌سازی امن است.

Access Token

Access Token توکنی است که در هر درخواست به Resource Server فرستاده می‌شود و دسترسی به منابع را تأیید می‌کند. عمر این توکن کوتاه است — در گوگل معمولاً یک ساعت. بعد از انقضا، اگر می‌خواهید همچنان به‌روز‌رسانی کنید، باید از Refresh Token استفاده کنید یا از کاربر بخواهید دوباره وارد شود.

Refresh Token

Refresh Token توکنی است که فقط برای دریافت Access Token جدید استفاده می‌شود. عمرش طولانی‌تر است — در گوگل معمولاً تا زمان ابطال توسط کاربر. توجه کنید که Refresh Token فقط برای اپلیکیشن‌هایی صادر می‌شود که Confidential Client باشند؛ در SPA و اپلیکیشن موبایل، معمولاً Refresh Token صادر نمی‌شود و به‌جای آن از الگوهای دیگری استفاده می‌شود.

نکته امنیتی مهم در Refresh Token: این توکن باید در سرور نگه داشته شود، نه در سمت کلاینت. اگر در localStorage ذخیره شود، در برابر حمله XSS آسیب‌پذیر است. اگر Client شما یک SPA است، بهترین رویکرد استفاده از یک Backend-for-Frontend (BFF) است که Refresh Token را در سرور نگه دارد و به کلاینت فقط Access Token بدهد.

ID Token

ID Token که فقط در OpenID Connect ظاهر می‌شود، یک JWT است که اطلاعات هویتی کاربر را نگه می‌دارد. این توکن برای احراز هویت استفاده می‌شود، نه برای دسترسی به منابع. عمرش هم معمولاً کوتاه است. در ورود با گوگل، ID Token منبع اصلی اطلاعات کاربر است و باید امضایش با کلید عمومی گوگل بررسی شود.

یک نکته مهم: در جریان OAuth، بعد از ورود موفق، توکن‌ها را با احتیاط نگه دارید. اگر سایت شما در مقابل XSS آسیب‌پذیر باشد و توکن در سمت کلاینت ذخیره شده باشد، مهاجم می‌تواند دسترسی‌های کاربر را بدزدد. برای اصول امنیتی مرتبط، چگونه نشست‌های کاربری را امن کنیم؟ راهنمای مکملی است.

OpenID Connect: لایه هویت روی OAuth

در ورود با گوگل، شما مستقیماً از OAuth استفاده نمی‌کنید؛ بلکه از OpenID Connect (OIDC) استفاده می‌کنید که یک لایه هویت روی OAuth 2.0 است. درک این تفکیک، از بسیاری از سردرگمی‌های پیاده‌سازی جلوگیری می‌کند.

سه اضافه اصلی OIDC نسبت به OAuth

OIDC سه چیز را به OAuth اضافه می‌کند. اول، Scope openid که فعال‌کننده OIDC است. دوم، ID Token که یک JWT امضاشده با اطلاعات هویت کاربر است. سوم، endpoint userinfo که اطلاعات بیشتر کاربر را در ازای Access Token برمی‌گرداند. این سه اضافه، OAuth را از یک پروتکل مجوز به یک پروتکل احراز هویت ارتقا می‌دهند.

Discovery Document و کلیدهای عمومی

OIDC یک مفهوم به‌نام Discovery Document را معرفی می‌کند که در آدرس .well-known/openid-configuration منتشر می‌شود. این سند، فهرست endpointهای احراز هویت را ارائه می‌دهد و به شما امکان می‌دهد که به‌طور خودکار تنظیمات را کشف کنید. علاوه بر این، OIDC مکانیزم JWKS را ارائه می‌دهد که کلیدهای عمومی برای بررسی امضای ID Token را منتشر می‌کند. گوگل کلیدهای عمومی خود را در آدرس مشخصی منتشر می‌کند که شما می‌توانید به‌طور دوره‌ای آن را به‌روز کنید.

تفاوت OIDC با OAuth

خلاصه تفاوت این دو: OAuth یک پروتکل مجوز است و OIDC یک پروتکل احراز هویت. OAuth به شما می‌گوید «این توکن چه دسترسی‌هایی دارد» و OIDC می‌گوید «این کاربر کیست». در ورود با گوگل، شما از هر دو استفاده می‌کنید: OAuth برای دریافت دسترسی به اطلاعات و OIDC برای دریافت هویت کاربر. این تفکیک، در پیاده‌سازی‌های پیچیده‌تر مثل ورود سازمانی (SSO) اهمیت بیشتری پیدا می‌کند.

اگر OAuth را یک کلید بدانیم که به قفل دسترسی می‌دهد، OIDC یک کارت شناسایی است که می‌گوید صاحب این کلید کیست.

پیاده‌سازی ورود با گوگل در وردپرس

حالا که جریان OAuth را می‌شناسید، پیاده‌سازی آن در وردپرس را مرور می‌کنیم. سه رویکرد اصلی در پروژه‌های واقعی وجود دارد که هر کدام مزایا و معایب خودش را دارد.

رویکرد اول: افزونه‌های آماده

ساده‌ترین رویکرد، استفاده از افزونه‌های آماده است که جریان OAuth را پیاده‌سازی کرده‌اند. اگر روی انتخاب افزونه دقت بیشتری می‌خواهید، افزونه وردپرس چیست و چگونه افزونه مناسب انتخاب کنیم معیارهای انتخاب را توضیح می‌دهد. نکته مهم در انتخاب افزونه: بررسی کنید که کد آن‌ها از PKCE پشتیبانی می‌کند، Redirect URI را به‌درست بررسی می‌کند، و ID Token را با کلید عمومی گوگل تأیید می‌کند. بعضی افزونه‌های قدیمی از PKCE پشتیبانی نمی‌کنند و امنیت پایین‌تری دارند.

رویکرد دوم: پیاده‌سازی اختصاصی با REST API

اگر پروژه شما به کنترل کامل نیاز دارد، می‌توانید جریان OAuth را با REST API وردپرس پیاده کنید. الگوی پایه سه endpoint دارد: یکی برای شروع جریان (که کاربر را به گوگل هدایت می‌کند)، یکی برای callback (که Authorization Code را دریافت و به توکن تبدیل می‌کند)، و یکی برای تأیید کاربر و صدور نشست وردپرس. ساختار کلی REST API در REST API در وردپرس توضیح داده شده است.

الگوی امنیتی مهم در این رویکرد: بعد از تأیید ID Token، کاربر را با استفاده از wp_set_auth_cookie وارد کنید و از تابع wp_safe_redirect برای هدایت استفاده کنید. هرگز کاربر را با پارامترهای URL هدایت نکنید چون در برابر حمله Open Redirect آسیب‌پذیر است. برای مرور سایر اصول امنیتی در کد وردپرس، نوشتن کد PHP امن برای وردپرس راهنمای مکمل است.

رویکرد سوم: Headless WordPress

در معماری Headless، بخش احراز هویت معمولاً در لایه فرانت‌اند مدیریت می‌شود و وردپرس فقط به‌عنوان CMS استفاده می‌شود. این معماری در پروژه‌های مدرن زیاد دیده می‌شود ولی چالش‌های خاص خودش را دارد. جریان OAuth در Headless، کاربر را به گوگل هدایت می‌کند، ID Token را در فرانت‌اند دریافت می‌کند، و بعد آن توکن را به یک endpoint در وردپرس می‌فرستد که آن را تأیید و کاربر را وارد می‌کند. اگر روی ساخت API اختصاصی کار می‌کنید، ساخت API اختصاصی برای وردپرس ساختار پایه را توضیح می‌دهد.

الگوی پردازش callback در وردپرس

در تمام سه رویکرد، پردازش callback الگوی مشابهی دارد:

function myplugin_oauth_callback() {
    if ( ! isset( $_GET['code'], $_GET['state'] ) ) {
        wp_die( esc_html__( 'Invalid OAuth callback.', 'myplugin' ) );
    }

    $state = sanitize_text_field( wp_unslash( $_GET['state'] ) );
    $saved_state = get_transient( 'myplugin_oauth_state_' . wp_get_session_token() );

    if ( ! $saved_state || ! hash_equals( $saved_state, $state ) ) {
        wp_die( esc_html__( 'State mismatch.', 'myplugin' ) );
    }

    delete_transient( 'myplugin_oauth_state_' . wp_get_session_token() );

    $code = sanitize_text_field( wp_unslash( $_GET['code'] ) );

    // ادامه: تبدیل code به token و ورود کاربر
}
add_action( 'admin_post_nopriv_myplugin_oauth_callback', 'myplugin_oauth_callback' );
add_action( 'admin_post_myplugin_oauth_callback', 'myplugin_oauth_callback' );

چند نکته ظریف در این الگو. اول، پارامتر state با استفاده از hash_equals بررسی می‌شود نه با عملگر ===؛ تفاوت این دو در مقاومت در برابر حمله timing attack است. دوم، state در یک Transient با کلید اختصاصی ذخیره می‌شود، نه در سشن یا کوکی. سوم، بعد از استفاده، state بلافاصله پاک می‌شود. اگر با مکانیزم Transient آشنایی کمتری دارید، ترنزینت وردپرس چیست و چگونه کش هوشمند بدون افزونه بسازیم الگوی ذخیره‌سازی موقت را توضیح می‌دهد.

الگوی ورود کاربر بعد از تأیید

بعد از دریافت ID Token و تأیید آن، باید کاربر را در وردپرس وارد کنید یا اگر کاربر جدید است، حساب بسازید. الگوی استاندارد:

$user_email = $id_token_payload['email'];
$user = get_user_by( 'email', $user_email );

if ( ! $user ) {
    $user_id = wp_insert_user( [
        'user_login' => sanitize_user( $id_token_payload['email'], true ),
        'user_email' => sanitize_email( $user_email ),
        'display_name' => sanitize_text_field( $id_token_payload['name'] ),
        'user_pass'   => wp_generate_password( 32, true, true ),
    ] );

    if ( is_wp_error( $user_id ) ) {
        wp_die( esc_html__( 'Could not create user.', 'myplugin' ) );
    }

    $user = get_user_by( 'id', $user_id );
}

wp_set_current_user( $user->ID );
wp_set_auth_cookie( $user->ID, true );

wp_safe_redirect( home_url() );
exit;

نکته امنیتی مهم: هنگام ساختن کاربر جدید، حتماً sanitize_user و sanitize_email را اعمال کنید و رمز عبور تصادفی بسازید تا از ورود مستقیم با رمز خالی جلوگیری شود. همچنین، اگر کاربر موجود است، اطمینان حاصل کنید که کاربر با گوگل احراز هویت شده و به‌درستی به حساب موجود نگاشت شده است. یکی از دام‌های رایج در این مرحله، نگاشت نادرست ایمیل‌های مشابه است که می‌تواند منجر به ورود کاربر به حساب اشتباه شود. اصول دقیق این نگاشت در کار با User Meta در کدنویسی وردپرس آمده است.

دام‌های امنیتی در پیاده‌سازی OAuth

در بازبینی پروژه‌های واقعی، پنج آسیب‌پذیری رایج در پیاده‌سازی OAuth دیده‌ام که هر کدام در روز حادثه، هزینه‌ساز می‌شوند. این بخش، چک‌لیست پیش از انتشار افزونه‌های من است.

دام اول: نبود بررسی State

رایج‌ترین دام. اگر پارامتر state در درخواست اولیه ارسال نشود یا در callback بررسی نشود، جریان OAuth در برابر حمله CSRF آسیب‌پذیر است. مهاجم می‌تواند کاربر را به callback با Authorization Code خودش هدایت کند و به‌طور ناخواسته، کاربر وارد حساب مهاجم شود. راه‌حل، همیشه تولید state تصادفی و بررسی دقیق آن در callback است.

دام دوم: نبود PKCE در Public Client

در اپلیکیشن‌های موبایل و SPA، نبود PKCE یک آسیب‌پذیری جدی است چون مهاجم می‌تواند Authorization Code را از URL استخراج کند و به توکن تبدیل کند. گوگل از سال ۲۰۱۹، استفاده از PKCE را برای همه Clientها توصیه می‌کند. در پروژه‌های واقعی، افزونه‌های قدیمی که PKCE را پشتیبانی نمی‌کنند، باید کنار گذاشته شوند.

دام سوم: عدم بررسی امضای ID Token

اگر کد شما فقط Payload ID Token را می‌خواند و امضا را بررسی نمی‌کند، مهاجم می‌تواند یک JWT جعلی بسازد و با آن، به‌عنوان هر کاربری وارد شود. این دام در افزونه‌های ساده‌انگارانه دیده می‌شود و راه‌حلش استفاده از کتابخانه‌های معتبری است که امضا را به‌طور خودکار بررسی می‌کنند.

دام چهارم: ذخیره Client Secret در Frontend

در بعضی پروژه‌ها، Client Secret در کد SPA قرار داده شده که در مرورگر کاربر قابل خواندن است. این یعنی هر کاربر می‌تواند Client Secret را ببیند و از آن برای جعل درخواست‌ها استفاده کند. راه‌حل، استفاده از PKCE در SPA و نگه‌داشتن Client Secret فقط در سرور است.

دام پنجم: نبود PKCE Verifier در سمت سرور

اگر PKCE را فعال کردید ولی code_verifier را در درخواست تبدیل کد به توکن نفرستادید، سرور گوگل درخواست را رد می‌کند. این دام در پیاده‌سازی‌های ناقص رایج است. راه‌حل، ذخیره code_verifier در Transient همراه با state و ارسال آن در درخواست Token است.

علاوه بر این پنج دام، دو نکته رایج دیگر هم وجود دارد. اول، ذخیره Access Token در localStorage که در برابر XSS آسیب‌پذیر است. دوم، طولانی گذاشتن عمر توکن که در صورت لورفتن، مدت طولانی معتبر می‌ماند. برای اصول امنیتی عمومی‌تر، امنیت وردپرس چیست و چرا حیاتی است راهنمای جامعی است.

هر دام امنیتی OAuth، تا روز حادثه در سکوت باقی می‌ماند؛ در آن روز، دیر است که راه‌حل جستجو کنیم.

پرسش‌های پرتکرار درباره OAuth و ورود با گوگل

OAuth چیست و چه تفاوتی با JWT دارد؟ OAuth یک پروتکل مجوز است که به یک اپلیکیشن اجازه دسترسی به منابع سرویس دیگر را می‌دهد. JWT یک فرمت داده برای انتقال امن اطلاعات بین دو طرف است. این دو، در کنار هم کار می‌کنند: OAuth از JWT به‌عنوان فرمت برای ID Token و Access Token استفاده می‌کند. برای درک دقیق‌تر تفاوت این دو، JWT چیست و چگونه احراز هویت را سبک‌تر می‌کند؟ مرجع مکملی است.

چرا ورود با گوگل برای کاربران ایرانی چالش دارد؟ چون دسترسی به سرویس گوگل از ایران محدود است. این محدودیت باعث می‌شود بعضی کاربران نتوانند در جریان OAuth شرکت کنند. راه‌حل، ارائه گزینه‌های جایگزین مثل ورود با ایمیل یا سرویس‌های داخلی است. جزئیات بیشتر در بهترین روش‌های احراز هویت کاربران کدامند؟ آمده است.

تفاوت Authorization Code Flow و Implicit Flow چیست؟ در Authorization Code Flow، کلاینت یک کد موقت می‌گیرد و آن را در سرور به توکن تبدیل می‌کند. در Implicit Flow، توکن مستقیم به مرورگر برگردانده می‌شود. Implicit Flow امروزه deprecated است و توصیه نمی‌شود چون در برابر حمله توکن لو رفتن آسیب‌پذیر است. برای همه Clientها، Authorization Code با PKCE رویکرد استاندارد است.

PKCE چیست و چرا ضروری است؟ PKCE مخفف Proof Key for Code Exchange است و یک لایه امنیتی به جریان Authorization Code اضافه می‌کند. در PKCE، کلاینت یک مقدار تصادفی تولید می‌کند و هش آن را با درخواست می‌فرستد. هنگام تبدیل کد به توکن، مقدار اصلی را می‌فرستد و سرور آن را با هش بررسی می‌کند. این لایه جلوی حمله رهگیری Authorization Code را می‌گیرد.

آیا Client Secret برای اپلیکیشن موبایل لازم است؟ نه. اپلیکیشن موبایل نمی‌تواند Client Secret را محرمانه نگه دارد، پس در این سناریو از PKCE استفاده می‌شود. اپلیکیشن موبایل، Public Client است و نباید Client Secret داشته باشد. اگر ابزار توسعه‌دهنده به شما گفت Client Secret را در اپلیکیشن قرار دهید، آن ابزار امن نیست.

چرا باید امضای ID Token را بررسی کنم؟ چون ID Token یک JWT است و در ظاهر می‌تواند جعلی باشد. اگر امضا را بررسی نکنید، مهاجم می‌تواند یک ID Token جعلی با هویت هر کاربری بسازد و به‌عنوان آن کاربر وارد شود. این آسیب‌پذیری در بازبینی‌های واقعی زیاد دیده می‌شود. اصول دقیق این بررسی در JWT چیست و چه کاربردی در احراز هویت دارد؟ آمده است.

تفاوت Refresh Token و Access Token چیست؟ Access Token توکنی است که به منابع دسترسی می‌دهد و عمرش کوتاه است (معمولاً یک ساعت). Refresh Token توکنی است که فقط برای گرفتن Access Token جدید استفاده می‌شود و عمرش طولانی است. Access Token در هر درخواست به سرور فرستاده می‌شود ولی Refresh Token فقط در زمان تازه‌سازی.

آیا می‌توانم ورود با گوگل را در وردپرس بدون افزونه پیاده کنم؟ بله، ولی نیازمند کار قابل توجهی است. باید endpoint callback، ذخیره state، تبدیل code به token، بررسی ID Token و ورود کاربر را پیاده کنید. اگر پروژه شما کوچک است، استفاده از افزونه معتبر انتخاب بهتری است. اگر پروژه سازمانی است و نیاز به کنترل کامل دارید، پیاده‌سازی اختصاصی منطقی است. راهنمای ساخت API اختصاصی در ساخت API اختصاصی برای وردپرس آمده است.

آیا Scopeهای گوگل باید تأیید شوند؟ فقط Scopeهای حساس. سه Scope پایه openid، email و profile نیازی به تأیید ندارند. اگر به Scopeهای حساس مثل Gmail، Drive یا Calendar نیاز دارید، باید فرآیند تأیید گوگل را طی کنید که ممکن است چند هفته طول بکشد.

چرا ورود با گوگل گاهی با تأخیر انجام می‌شود؟ دلایل متعدد. اول، کیفیت اتصال به سرویس گوگل. دوم، کندی در تبدیل code به token که یک درخواست سرور-به-سرور است. سوم، کوئری‌های اضافه در سمت سرور شما. اگر با گلوگاه‌های سرعت سایت آشنا نیستید، تاثیر هاست بر سرعت سایت چقدر است تحلیل دقیقی دارد.

آیا می‌توانم ورود با گوگل را با احراز هویت دو مرحله‌ای ترکیب کنم؟ بله. معمولاً دو الگو وجود دارد. الگوی اول، اعتماد کامل به گوگل و نبود مرحله دوم (که توصیه نمی‌شود). الگوی دوم، افزودن لایه دوم مثل TOTP بعد از ورود موفق با گوگل. الگوی دوم امنیت بالاتری دارد ولی تجربه کاربری را پیچیده‌تر می‌کند. برای سایت‌های حساس، الگوی دوم انتخاب بهتری است. اصول این ترکیب در پیاده‌سازی MFA در اپلیکیشن‌های وب آمده است.

آیا می‌توان از OAuth برای ورود بدون رمز استفاده کرد؟ بله، این یکی از سناریوهای رو به رشد است. با OAuth، کاربر نیازی به رمز جدید ندارد چون با گوگل احراز هویت می‌شود. مقایسه کامل این رویکرد با سایر روش‌های ورود بدون رمز در ورود بدون رمز عبور چه مزایا و معایبی دارد؟ آمده است.

آیا OAuth می‌تواند برای احراز هویت سازمانی استفاده شود؟ بله، با ترکیب OIDC و استاندارد SAML، می‌توان احراز هویت سازمانی را پیاده کرد. این رویکرد در معماری SSO رایج است و اصول آن در راه‌اندازی SSO برای تیم‌های دورکار توضیح داده شده است.

انتخاب آگاهانه، نه تقلید کورکورانه

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

در پروژه‌های خودم، سه اصل ساده را رعایت می‌کنم که در طول این نوشته به‌تدریج باز کردم. اصل اول: OAuth یک پروتکل مجوز است، نه احراز هویت. برای احراز هویت، OIDC لازم است. اصل دوم: در Public Client، PKCE جایگزین Client Secret است و نباید نادیده گرفته شود. اصل سوم: هر چه در شبکه می‌آید، باید تأیید شود — state، ID Token، هر چیز دیگر. این سه اصل، چارچوب تصمیم من در همه پروژه‌های واقعی است.

اگر در ابتدای مسیر یادگیری هستید، سه تمرین را پیشنهاد می‌کنم. اول، در یک پروژه تستی، یک OAuth Client در Google Cloud Console بسازید و جریان را به‌طور دستی با پست‌من یا curl شبیه‌سازی کنید تا هر گام را از نزدیک ببینید. دوم، در محیط staging، یک آسیب‌پذیری «نبود بررسی state» ایجاد کنید و ببینید چطور یک مهاجم می‌تواند از آن سوءاستفاده کند. سوم، یک ID Token جعلی بسازید و ببینید کد شما چطور باید آن را رد کند. این سه تمرین، در چند ساعت، درک عمیقی از OAuth به شما می‌دهد که هیچ مقاله‌ای جایگزینش نمی‌شود.

اگر تجربه‌ای از پیاده‌سازی OAuth در پروژه واقعی دارید — چه با موفقیت، چه با دام‌های امنیتی که فقط در آتش دیده می‌شوند — در دیدگاه بنویسید. آن تجربه برای کسی که همین امروز تصمیم می‌گیرد ورود با گوگل را به سایتش اضافه کند یا نه، ارزشمندتر از هر مستند رسمی است. 🔑