سی ام اس نویسسی ام اس نویسیاد بگیر، سیستم خودتو بنویس
مجلهتماس با ما
سی ام اس نویسسی ام اس نویس

CMS نویس مرجعی برای آموزش برنامه نویسی وب و PHP، توسعه افزونه‌های اختصاصی وردپرس، طراحی ورک‌فلو و اتوماسیون با n8n و توسعه CMS های اختصاصی است. هدف ما ارائه آموزش‌های کاربردی و راهکارهای حرفه‌ای برای توسعه‌دهندگان، کسب‌وکارها و علاقه‌مندان به دنیای برنامه‌نویسی است.

پیوندهای سریع
محصولات فیزیکیمحصولات دیجیتالدوره‌های آموزشی
سی ام اس نویس
مجله سی ام اس نویستماس با ماپنل کاربری
سی ام اس نویس — تمامی حقوق برای سی ام اس نویس محفوظ است. © 1406 تماس با ما09307711941
ورود با Google در PHP با OAuth 2.0 و OpenID Connect؛ آموزش عملی از صفر تا Production

ورود با Google در PHP با OAuth 2.0 و OpenID Connect؛ آموزش عملی از صفر تا Production

ممدیر کل سیستم ۳۰ شهریور ۱۴۰۵ ۲۱ دقیقه مطالعه ۱ بازدید
ورود با Google در PHP با OAuth 2.0 و OpenID Connect؛ آموزش عملی از صفر تا Production

مقدمه

امروزه بسیاری از وب‌سایت‌ها به‌جای اینکه کاربر را مجبور کنند برای سایت یک رمز عبور جدید ایجاد کند، امکان ورود با حساب‌های سرویس‌هایی مانند Google را فراهم می‌کنند.

احتمالاً در بسیاری از سایت‌ها دکمه‌ای مانند:

ورود با Google

را دیده‌اید.

اما پشت همین دکمه ساده، چند فرآیند امنیتی مهم اتفاق می‌افتد:

  • OAuth 2.0
  • OpenID Connect
  • Authorization Code
  • Redirect URI
  • Access Token
  • ID Token
  • State
  • Nonce
  • PKCE
  • اعتبارسنجی امضای توکن
  • ایجاد یا اتصال حساب کاربر در دیتابیس

در مقاله‌های قبلی درباره OAuth 2.0 و OpenID Connect به‌صورت مفهومی صحبت کردیم. در این مقاله می‌خواهیم یک قدم جلوتر برویم و یک سیستم واقعی Login با Google در PHP طراحی کنیم.

هدف این است که کاربر روی دکمه «ورود با Google» کلیک کند، وارد حساب Google خود شود، اجازه دسترسی را تأیید کند و در نهایت به سایت PHP ما برگردد و یک Session محلی برای او ایجاد شود.


# 1. Login با Google دقیقاً چگونه کار می‌کند؟

قبل از نوشتن کد، باید معماری کلی سیستم را بدانیم.

فرآیند معمول به شکل زیر است:


User

 │

 │ کلیک روی ورود با Google

 ▼

PHP Application

 │

 │ Authorization Request

 ▼

Google

 │

 │ Login + Consent

 ▼

Google Authorization Server

 │

 │ Authorization Code

 ▼

PHP Callback

 │

 │ Code Exchange

 ▼

Google Token Endpoint

 │

 │ ID Token + Access Token

 ▼

PHP Application

 │

 │ Verify ID Token

 ▼

Database

 │

 │ Find/Create User

 ▼

Local PHP Session

 │

 ▼

Dashboard

نکته مهم این است که سایت ما نباید رمز عبور Google کاربر را دریافت کند.

کاربر مستقیماً در محیط Google احراز هویت می‌شود.


# 2. OAuth 2.0 و OpenID Connect چه نقشی دارند؟

در این پروژه دو استاندارد مهم داریم.

OAuth 2.0

OAuth 2.0 عمدتاً برای Authorization یا اعطای دسترسی استفاده می‌شود.

برای مثال:


آیا این برنامه اجازه دارد به اطلاعات مشخصی از حساب Google دسترسی داشته باشد؟

اما OAuth 2.0 به‌تنهایی یک استاندارد کامل برای شناسایی هویت کاربر نیست.

اینجاست که OpenID Connect وارد می‌شود.

OpenID Connect

OpenID Connect روی OAuth 2.0 ساخته شده و برای Authentication یا احراز هویت هویت کاربر استفاده می‌شود.

در Login با Google معمولاً از این Scopeها استفاده می‌کنیم:


openid profile email

Scope openid باعث می‌شود جریان OpenID Connect فعال شود.


# 3. پیش‌نیازهای پروژه

برای این آموزش به موارد زیر نیاز داریم:

  • PHP 8+
  • یک وب‌سرور
  • Session فعال در PHP
  • MySQL یا MariaDB
  • HTTPS در محیط Production
  • یک حساب Google
  • Google Cloud Project

برای محیط توسعه می‌توانیم از آدرس localhost استفاده کنیم.

برای مثال:


http://localhost:8000

اما در Production باید از HTTPS استفاده کنیم:


https://example.com


# 4. ساخت Google Cloud Project

ابتدا وارد Google Cloud Console شوید.

یک Project جدید ایجاد کنید.

برای مثال نام پروژه:


PHP Google Login

بعد از ایجاد Project باید تنظیمات OAuth را انجام دهیم.

در بخش مربوط به Google Authentication، اطلاعات مربوط به OAuth Consent Screen یا Branding را تنظیم کنید.

اطلاعاتی مانند:


Application name

Support email

Developer contact information

را وارد کنید.

اگر برنامه شما در حالت تست باشد، ممکن است لازم باشد حساب‌های آزمایشی را نیز مشخص کنید.


# 5. ساخت OAuth Client ID

حالا باید برای برنامه PHP خود یک Client ایجاد کنیم.

نوع Application را:


Web application

انتخاب کنید.

برای Client یک نام مشخص انتخاب کنید:


PHP Website

سپس مهم‌ترین قسمت یعنی Redirect URI را تنظیم کنید.

برای مثال:


http://localhost:8000/auth/google/callback.php

و در Production:


https://example.com/auth/google/callback.php

Redirect URI اهمیت بسیار زیادی دارد.

Google بعد از احراز هویت کاربر، Authorization Code را فقط به URIهایی برمی‌گرداند که برای Client تعریف شده‌اند.

حتی یک تفاوت کوچک می‌تواند باعث خطای:


redirect_uri_mismatch

شود.

مثلاً این دو URI الزاماً یکسان محسوب نمی‌شوند:


https://example.com/auth/google/callback

و:


https://example.com/auth/google/callback/

بنابراین Redirect URI را دقیقاً همان چیزی قرار دهید که در برنامه PHP استفاده می‌کنید.


# 6. Client ID و Client Secret

بعد از ساخت Client معمولاً دو مقدار مهم دریافت می‌کنید:


Client ID

Client Secret

مثلاً:


Client ID:

123456789-example.apps.googleusercontent.com

و:


Client Secret:

GOCSPX-example-secret

Client Secret را نباید داخل JavaScript یا HTML قرار دهید.

همچنین آن را نباید در GitHub یا سایر Repositoryهای عمومی قرار دهید.

بهتر است اطلاعات حساس را در Environment Variable نگهداری کنیم.

برای مثال:


GOOGLE_CLIENT_ID=your-client-id

GOOGLE_CLIENT_SECRET=your-client-secret

GOOGLE_REDIRECT_URI=https://example.com/auth/google/callback.php


# 7. ساختار پروژه PHP

برای این آموزش ساختار ساده زیر را در نظر می‌گیریم:


project/

│

├── config/

│   └── google.php

│

├── auth/

│   └── google/

│       ├── login.php

│       └── callback.php

│

├── dashboard.php

├── logout.php

└── index.php

در پروژه واقعی می‌توانیم همین معماری را داخل MVC یا Laravel پیاده‌سازی کنیم.


# 8. ساخت جدول کاربران

فرض کنیم جدول کاربران ما به شکل زیر باشد:


CREATE TABLE users (

    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

    name VARCHAR(150) NOT NULL,

    email VARCHAR(255) NOT NULL UNIQUE,

    avatar_url TEXT NULL,

    email_verified_at DATETIME NULL,

    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP

);

اما یک نکته مهم وجود دارد.

نباید فقط Google Email را به‌عنوان هویت خارجی کاربر در نظر بگیریم.

بهتر است یک جدول جدا برای Identityها داشته باشیم.


# 9. جدول User Identity

مثلاً:


CREATE TABLE user_identities (

    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,

    user_id BIGINT UNSIGNED NOT NULL,

    provider VARCHAR(50) NOT NULL,

    issuer VARCHAR(255) NOT NULL,

    subject VARCHAR(255) NOT NULL,

    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,



    UNIQUE KEY unique_provider_identity (

        provider,

        issuer,

        subject

    ),



    FOREIGN KEY (user_id)

        REFERENCES users(id)

        ON DELETE CASCADE

);

چرا این کار بهتر است؟

چون یک کاربر ممکن است در آینده بتواند چند روش ورود داشته باشد:


Google

GitHub

Facebook

Apple

Password

بنابراین معماری ما می‌تواند چنین چیزی داشته باشد:


User

 │

 ├── Google Identity

 ├── GitHub Identity

 └── Password


# 10. ساخت Configuration

فایل:


config/google.php

را ایجاد کنید.

مثلاً:


<?php



return [

    'client_id' => getenv('GOOGLE_CLIENT_ID'),

    'client_secret' => getenv('GOOGLE_CLIENT_SECRET'),

    'redirect_uri' => getenv('GOOGLE_REDIRECT_URI'),



    'authorization_endpoint' =>

        'https://accounts.google.com/o/oauth2/v2/auth',



    'token_endpoint' =>

        'https://oauth2.googleapis.com/token',



    'issuer' =>

        'https://accounts.google.com',

];

در پروژه Production بهتر است Secretها مستقیماً داخل Source Code نوشته نشوند.


# 11. ساخت دکمه ورود با Google

در صفحه Login:


<a href="/auth/google/login.php">

    ورود با Google

</a>

می‌توانیم ظاهر آن را با CSS یا Tailwind به شکل یک دکمه حرفه‌ای طراحی کنیم.

اما پشت این دکمه اتفاق اصلی در فایل login.php رخ می‌دهد.


# 12. ساخت Authorization Request

فایل:


auth/google/login.php

را ایجاد کنید.

ابتدا Session را فعال می‌کنیم:


<?php



session_start();



$config = require __DIR__ . '/../../config/google.php';

حالا باید یک مقدار state تولید کنیم.


# 13. چرا State مهم است؟

state یکی از مکانیزم‌های مهم امنیتی OAuth است.

مثلاً:


$state = bin2hex(random_bytes(32));



$_SESSION['google_oauth_state'] = $state;

بعد این مقدار را به Google ارسال می‌کنیم.

وقتی Google کاربر را به سایت ما برگرداند، باید بررسی کنیم که:


state برگشتی == state ذخیره‌شده

اگر برابر نبود، درخواست را رد می‌کنیم.

این کار برای جلوگیری از حملات CSRF در جریان OAuth اهمیت دارد.


# 14. استفاده از Nonce

برای OpenID Connect بهتر است یک nonce نیز ایجاد کنیم:


$nonce = bin2hex(random_bytes(32));



$_SESSION['google_oidc_nonce'] = $nonce;

Nonce بعداً باید با مقدار موجود در ID Token مقایسه شود.


# 15. PKCE

یکی دیگر از مکانیزم‌های امنیتی مهم، PKCE است.

ابتدا یک Code Verifier تصادفی ایجاد می‌کنیم:


$codeVerifier = rtrim(

    strtr(

        base64_encode(random_bytes(64)),

        '+/',

        '-_'

    ),

    '='

);

سپس Code Challenge را ایجاد می‌کنیم:


$codeChallenge = rtrim(

    strtr(

        base64_encode(

            hash(

                'sha256',

                $codeVerifier,

                true

            )

        ),

        '+/',

        '-_'

    ),

    '='

);

و آن را در Session نگه می‌داریم:


$_SESSION['google_code_verifier'] = $codeVerifier;


# 16. ساخت Authorization URL

حالا پارامترهای OAuth را آماده می‌کنیم:


$params = [

    'client_id' => $config['client_id'],

    'redirect_uri' => $config['redirect_uri'],

    'response_type' => 'code',

    'scope' => 'openid profile email',

    'state' => $state,

    'nonce' => $nonce,

    'code_challenge' => $codeChallenge,

    'code_challenge_method' => 'S256',

    'access_type' => 'offline',

];

سپس URL را ایجاد می‌کنیم:


$authorizationUrl =

    $config['authorization_endpoint']

    . '?'

    . http_build_query($params);

و کاربر را به Google می‌فرستیم:


header('Location: ' . $authorizationUrl);

exit;

بنابراین فایل Login ما در حالت ساده می‌تواند چنین باشد:


<?php



session_start();



$config = require __DIR__ . '/../../config/google.php';



$state = bin2hex(random_bytes(32));

$nonce = bin2hex(random_bytes(32));



$codeVerifier = rtrim(

    strtr(

        base64_encode(random_bytes(64)),

        '+/',

        '-_'

    ),

    '='

);



$codeChallenge = rtrim(

    strtr(

        base64_encode(

            hash('sha256', $codeVerifier, true)

        ),

        '+/',

        '-_'

    ),

    '='

);



$_SESSION['google_oauth_state'] = $state;

$_SESSION['google_oidc_nonce'] = $nonce;

$_SESSION['google_code_verifier'] = $codeVerifier;



$params = [

    'client_id' => $config['client_id'],

    'redirect_uri' => $config['redirect_uri'],

    'response_type' => 'code',

    'scope' => 'openid profile email',

    'state' => $state,

    'nonce' => $nonce,

    'code_challenge' => $codeChallenge,

    'code_challenge_method' => 'S256',

];



$url = $config['authorization_endpoint']

    . '?'

    . http_build_query($params);



header('Location: ' . $url);

exit;


# 17. کاربر در Google چه اتفاقی را می‌بیند؟

بعد از اجرای این کد، کاربر وارد محیط Google می‌شود.

اگر Login نباشد، Google از او می‌خواهد وارد حسابش شود.

سپس در صورت نیاز صفحه Consent نمایش داده می‌شود.

بعد از موفقیت، Google کاربر را به:


/auth/google/callback.php

برمی‌گرداند.

معمولاً چیزی شبیه این دریافت می‌کنیم:


?code=4/0A...

&scope=openid%20email%20profile

&state=...


# 18. چرا Authorization Code را مستقیماً Login Token در نظر نمی‌گیریم؟

چون:


Authorization Code

هنوز Access Token یا ID Token نیست.

ما باید این Code را از طریق Server خودمان به Google Token Endpoint ارسال کنیم.

این مرحله اصطلاحاً:


Authorization Code Exchange

نام دارد.


# 19. بررسی State

در callback.php ابتدا Session را فعال می‌کنیم:


<?php



session_start();



$config = require __DIR__ . '/../../config/google.php';

سپس:


if (

    empty($_GET['state']) ||

    empty($_SESSION['google_oauth_state']) ||

    !hash_equals(

        $_SESSION['google_oauth_state'],

        $_GET['state']

    )

) {

    http_response_code(400);

    exit('Invalid OAuth state.');

}

اگر State معتبر نباشد، فرآیند را متوقف می‌کنیم.


# 20. دریافت Authorization Code


if (empty($_GET['code'])) {

    http_response_code(400);

    exit('Authorization code is missing.');

}



$code = $_GET['code'];

حالا باید Code را با Tokenها exchange کنیم.


# 21. ارسال Code به Token Endpoint

با PHP و cURL می‌توانیم درخواست POST ارسال کنیم:


$postData = [

    'code' => $code,

    'client_id' => $config['client_id'],

    'client_secret' => $config['client_secret'],

    'redirect_uri' => $config['redirect_uri'],

    'grant_type' => 'authorization_code',

    'code_verifier' => $_SESSION['google_code_verifier'],

];

سپس:


$ch = curl_init($config['token_endpoint']);



curl_setopt_array($ch, [

    CURLOPT_POST => true,

    CURLOPT_POSTFIELDS => http_build_query($postData),

    CURLOPT_RETURNTRANSFER => true,

    CURLOPT_HTTPHEADER => [

        'Content-Type: application/x-www-form-urlencoded',

    ],

]);



$response = curl_exec($ch);



if ($response === false) {

    throw new RuntimeException(

        curl_error($ch)

    );

}



curl_close($ch);

پاسخ را JSON Decode می‌کنیم:


$tokens = json_decode(

    $response,

    true

);



if (!is_array($tokens)) {

    throw new RuntimeException(

        'Invalid token response.'

    );

}


# 22. Token Response

در یک جریان موفق ممکن است پاسخ شامل مواردی مانند این باشد:


{

    "access_token": "...",

    "expires_in": 3599,

    "scope": "openid profile email",

    "token_type": "Bearer",

    "id_token": "..."

}

مهم‌ترین مقدار برای Login با OpenID Connect:


id_token

است.


# 23. ID Token چیست؟

ID Token معمولاً یک JWT است.

ساختار JWT به شکل زیر است:


HEADER.PAYLOAD.SIGNATURE

مثلاً:


eyJhbGciOiJSUzI1NiJ9

.

eyJpc3MiOiJodHRwczovL2...

.

SIGNATURE

اما یک اشتباه بسیار خطرناک وجود دارد.

نباید فقط JWT را Decode کنیم و به اطلاعات آن اعتماد کنیم.

این کار کافی نیست:


base64_decode(...)

چون Decode کردن به معنی Verify کردن نیست.


# 24. اعتبارسنجی واقعی ID Token

در Production باید حداقل موارد زیر بررسی شوند:


Signature

iss

aud

exp

nonce

مثلاً:

issuer

باید مربوط به Google باشد:


https://accounts.google.com

audience

باید Client ID برنامه شما باشد.

expiration

توکن نباید منقضی شده باشد.

nonce

باید با Nonce ذخیره‌شده در Session برابر باشد.

signature

امضای JWT باید با کلید عمومی معتبر Google بررسی شود.


# 25. چرا Signature Verification مهم است؟

فرض کنید مهاجم یک JWT جعلی بسازد:


{

    "email": "admin@example.com",

    "name": "Admin"

}

اگر برنامه فقط Payload را Decode کند، ممکن است به اطلاعات جعلی اعتماد کند.

بنابراین:


Decode ≠ Verify

همیشه باید این دو مفهوم را از هم جدا کنیم.


# 26. دریافت اطلاعات کاربر

پس از اعتبارسنجی موفق ID Token می‌توانیم اطلاعات مورد نیاز را استخراج کنیم.

برای مثال:


sub

email

email_verified

name

picture

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


sub

است.

sub شناسه پایدار کاربر در محدوده Issuer است.

برای Identity Mapping بهتر است ترکیب:


issuer + subject

را ذخیره کنیم.


# 27. چرا فقط Email کافی نیست؟

فرض کنید کاربر:


user@example.com

دارد.

اگر فقط Email را به‌عنوان شناسه خارجی ذخیره کنیم، معماری سیستم برای چند Provider محدود می‌شود.

ساختار بهتر:


provider = google

issuer   = https://accounts.google.com

subject  = 123456789...

است.

به این ترتیب هویت خارجی کاربر به شکل دقیق‌تری مشخص می‌شود.


# 28. پیدا کردن کاربر در دیتابیس

ابتدا Identity را جستجو می‌کنیم:


SELECT user_id

FROM user_identities

WHERE provider = ?

AND issuer = ?

AND subject = ?

LIMIT 1;

اگر پیدا شد، کاربر قبلاً با Google وارد شده است.

اگر پیدا نشد، می‌توانیم فرآیند ایجاد یا اتصال حساب را انجام دهیم.


# 29. ایجاد کاربر جدید

در صورتی که کاربر وجود نداشته باشد:


INSERT INTO users (

    name,

    email,

    avatar_url,

    email_verified_at

)

VALUES (?, ?, ?, ?);

سپس Identity را ثبت می‌کنیم:


INSERT INTO user_identities (

    user_id,

    provider,

    issuer,

    subject

)

VALUES (?, ?, ?, ?);

بهتر است این دو عملیات داخل Transaction انجام شوند.

مثلاً:


$pdo->beginTransaction();



try {



    // Create user



    // Create identity



    $pdo->commit();



} catch (Throwable $e) {



    $pdo->rollBack();



    throw $e;

}


# 30. ساخت Session محلی

بعد از پیدا کردن یا ایجاد User باید Session محلی سایت را ایجاد کنیم.

یکی از مهم‌ترین اقدامات:


session_regenerate_id(true);

است.

سپس:


$_SESSION['user_id'] = $userId;

می‌توانیم Session را ایجاد کنیم.

مثلاً:


session_regenerate_id(true);



$_SESSION['user_id'] = $userId;

$_SESSION['authenticated'] = true;

و کاربر را به Dashboard منتقل کنیم:


header('Location: /dashboard.php');

exit;


# 31. Session محلی با Google Session فرق دارد

یک نکته مهم:

وقتی کاربر با Google Login می‌کند، سایت شما یک Session داخلی ایجاد می‌کند.

مثلاً:


Google Session

        │

        ▼

Google Authentication

        │

        ▼

OIDC ID Token

        │

        ▼

Your Application

        │

        ▼

PHP Session

بنابراین بعد از Login موفق، سایت شما معمولاً برای درخواست‌های بعدی به Session داخلی خودش تکیه می‌کند.


# 32. Logout

برای Logout کافی نیست که فقط صفحه را Redirect کنیم.

Session را باید واقعاً Destroy کنیم.

مثلاً:


<?php



session_start();



$_SESSION = [];



if (ini_get('session.use_cookies')) {



    $params = session_get_cookie_params();



    setcookie(

        session_name(),

        '',

        time() - 42000,

        $params['path'],

        $params['domain'],

        $params['secure'],

        $params['httponly']

    );

}



session_destroy();



header('Location: /');

exit;

توجه کنید که Logout از سایت شما الزاماً به معنی Logout شدن از حساب Google نیست.


# 33. مدیریت خطاهای OAuth

در Callback ممکن است Google به‌جای Code پارامتر Error ارسال کند.

مثلاً:


error=access_denied

پس بهتر است بررسی کنیم:


if (!empty($_GET['error'])) {



    $error = $_GET['error'];



    http_response_code(400);



    exit(

        'Google authentication failed.'

    );

}

در محیط Production بهتر است جزئیات حساس خطا را مستقیماً به کاربر نمایش ندهیم.


# 34. خطای معروف redirect_uri_mismatch

یکی از رایج‌ترین خطاها:


Error 400: redirect_uri_mismatch

است.

معمولاً یعنی Redirect URI ارسال‌شده توسط برنامه با URI ثبت‌شده در Google یکسان نیست.

مثلاً برنامه:


http://localhost:8000/auth/google/callback.php

ارسال می‌کند.

اما Google این را ثبت کرده است:


http://localhost:8000/auth/google/callback

این دو یکی نیستند.


# 35. HTTPS در Production

در Production حتماً از HTTPS استفاده کنید.

به‌خصوص وقتی با:


Session Cookie

Authorization Code

Access Token

ID Token

کار می‌کنیم.

Cookieهای Session نیز بهتر است ویژگی‌های امنیتی مناسب داشته باشند:


Secure

HttpOnly

SameSite

برای مثال:


session_set_cookie_params([

    'secure' => true,

    'httponly' => true,

    'samesite' => 'Lax',

]);

البته این تنظیم باید قبل از session_start() انجام شود.


# 36. State، Nonce و PKCE را با هم اشتباه نگیریم

این سه مقدار کاربرد یکسانی ندارند.

مقدارکاربرد
stateمحافظت از جریان OAuth در برابر CSRF
nonceاتصال درخواست OIDC به ID Token
code_verifierمحافظت PKCE در Authorization Code Flow

بنابراین استفاده از یکی به‌جای دیگری درست نیست.


# 37. معماری کامل Login با Google

در نهایت معماری ما چنین می‌شود:


                    ┌──────────────┐

                    │    Google    │

                    └──────┬───────┘

                           │

                    Authorization

                           │

                           ▼

┌─────────────┐     ┌───────────────┐

│    User     │────▶│ PHP Login URL │

└─────────────┘     └───────┬───────┘

                            │

                            ▼

                         Google

                            │

                      Authorization

                            │

                            ▼

                    callback.php

                            │

                     Verify State

                            │

                            ▼

                     Token Exchange

                            │

                            ▼

                      ID Token

                            │

                     Verify Signature

                            │

                            ▼

                     Find / Create

                          User

                            │

                            ▼

                     PHP Session

                            │

                            ▼

                        Dashboard


# 38. ساختار پیشنهادی Production

برای پروژه واقعی بهتر است مسئولیت‌ها را از هم جدا کنیم:


app/

│

├── Auth/

│   ├── GoogleOAuthService.php

│   ├── OidcTokenVerifier.php

│   └── UserIdentityService.php

│

├── Database/

│

└── Support/

    └── HttpClient.php

و:


config/

└── google.php

همچنین:


public/

└── auth/

    └── google/

        ├── login.php

        └── callback.php

در پروژه‌های بزرگ‌تر بهتر است این منطق مستقیماً در Controller و Service Layer قرار بگیرد.


# 39. آیا باید ID Token را خودمان Verify کنیم؟

برای یادگیری، دانستن فرآیند Verification بسیار مهم است.

اما در Production بهتر است از یک کتابخانه معتبر و به‌روز برای OAuth/OIDC استفاده کنیم تا مواردی مانند:


JWT Signature

JWKS

Key Rotation

Claims Validation

Token Validation

به‌درستی مدیریت شوند.

یک گزینه شناخته‌شده در اکوسیستم PHP، Google API Client است؛ ولی انتخاب نسخه و روش استفاده باید بر اساس مستندات و نسخه فعلی پروژه انجام شود.

نکته مهم این است که استفاده از کتابخانه نباید باعث شود مفهوم امنیتی سیستم را ندانیم.


# 40. اشتباهات رایج در Login با Google

اشتباه اول: قرار دادن Client Secret در JavaScript

غلط:


const clientSecret = "secret";

Client Secret باید سمت Server باقی بماند.


اشتباه دوم: اعتماد به Payload بدون Verification

غلط:


$payload = base64_decode($jwtPayload);

Decode کردن به معنی معتبر بودن Token نیست.


اشتباه سوم: نداشتن State

OAuth بدون State می‌تواند در برابر حملات مربوط به جریان Authorization آسیب‌پذیر شود.


اشتباه چهارم: حذف Nonce در OIDC

وقتی از OpenID Connect استفاده می‌کنیم، Nonce باید بخشی از فرآیند اعتبارسنجی باشد.


اشتباه پنجم: استفاده نکردن از PKCE

در Authorization Code Flow، استفاده صحیح از PKCE یک لایه امنیتی مهم اضافه می‌کند.


اشتباه ششم: ذخیره Token خام بدون نیاز

Access Token و Refresh Token اطلاعات حساس هستند.

نباید آنها را بدون نیاز و بدون محافظت مناسب در دیتابیس یا Log ذخیره کرد.


اشتباه هفتم: استفاده از Email به‌عنوان تنها Identity Key

بهتر است Identity خارجی را با:


issuer + subject

مدیریت کنیم.


اشتباه هشتم: استفاده از HTTP در Production

برای Authentication از HTTP معمولی استفاده نکنید.

Production باید HTTPS داشته باشد.


# 41. چک‌لیست امنیت Login با Google

قبل از Production این موارد را بررسی کنید:


☑ HTTPS فعال است

☑ Client Secret سمت Server است

☑ State تصادفی تولید می‌شود

☑ State بررسی می‌شود

☑ Nonce تولید می‌شود

☑ Nonce بررسی می‌شود

☑ PKCE استفاده می‌شود

☑ Authorization Code فقط یک‌بار مصرف می‌شود

☑ ID Token واقعاً Verify می‌شود

☑ Signature بررسی می‌شود

☑ iss بررسی می‌شود

☑ aud بررسی می‌شود

☑ exp بررسی می‌شود

☑ email_verified در صورت نیاز بررسی می‌شود

☑ Session بعد از Login regenerate می‌شود

☑ Session Cookie امن است

☑ Tokenها در Log ذخیره نمی‌شوند

☑ Client Secret در Git قرار نگرفته است

☑ Redirect URI دقیقاً تنظیم شده است

☑ Authorization و Authentication از هم جدا هستند


# 42. تفاوت Login با Google و API Access

این دو موضوع را نباید با هم اشتباه بگیریم.

وقتی کاربر با Google Login می‌کند:


OpenID Connect

برای احراز هویت هویت کاربر استفاده می‌شود.

اما اگر برنامه شما بخواهد به یک API خاص Google دسترسی داشته باشد، ممکن است:


OAuth 2.0 Access Token

مورد استفاده قرار گیرد.

مثلاً:


Authentication:

Who is this user?



Authorization:

What can this application access?

این تفکیک در طراحی سیستم‌های امن بسیار مهم است.


# 43. Login با Google در Laravel

همین معماری را می‌توانیم در Laravel نیز پیاده‌سازی کنیم.

ساختار کلی مثلاً:


Google Login

      ↓

Controller

      ↓

Google OAuth Service

      ↓

Google

      ↓

Callback

      ↓

UserIdentity

      ↓

User

      ↓

Laravel Session

در Laravel معمولاً Service Layer و Packageهای معتبر باعث می‌شوند بخش زیادی از جزئیات HTTP و OAuth ساده‌تر شود.

اما مفاهیم اصلی همان‌هایی هستند که در PHP خام بررسی کردیم.


# 44. Google Login فقط یک دکمه نیست

یکی از مهم‌ترین نکاتی که باید از این آموزش یاد بگیریم این است که:


Login with Google

صرفاً یک Button نیست.

پشت این Button یک زنجیره امنیتی کامل وجود دارد:


OAuth 2.0

        +

OpenID Connect

        +

Authorization Code

        +

PKCE

        +

State

        +

Nonce

        +

ID Token Verification

        +

User Identity Mapping

        +

Secure Session

اگر یکی از این قسمت‌ها به شکل اشتباه پیاده‌سازی شود، ممکن است امنیت کل سیستم تحت تأثیر قرار بگیرد.


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

آیا برای Login با Google باید رمز عبور کاربر را ذخیره کنیم؟

خیر.

رمز عبور Google در اختیار برنامه شما قرار نمی‌گیرد.


آیا OAuth 2.0 همان Login است؟

خیر.

OAuth 2.0 عمدتاً برای Authorization طراحی شده است.

برای Authentication معمولاً از OpenID Connect استفاده می‌شود.


آیا ID Token همان Access Token است؟

خیر.

ID Token برای انتقال اطلاعات هویت کاربر در OIDC است.

Access Token برای دسترسی به Resource Server/API استفاده می‌شود.


آیا می‌توانیم فقط Email کاربر را ذخیره کنیم؟

می‌توانیم Email را در جدول User ذخیره کنیم، اما برای اتصال Identity خارجی بهتر است از ترکیب:


issuer + subject

استفاده کنیم.


آیا در localhost می‌توان Login با Google را تست کرد؟

بله، برای محیط توسعه می‌توان Redirect URI مربوط به localhost را در تنظیمات OAuth Client قرار داد.

اما Production باید روی HTTPS باشد.


آیا می‌توانیم Google Login را بدون کتابخانه PHP بنویسیم؟

از نظر فنی بله.

با PHP و cURL می‌توان درخواست‌های OAuth را ارسال کرد.

اما در Production باید اعتبارسنجی Token و مدیریت کلیدها با دقت بالا انجام شود و استفاده از کتابخانه معتبر می‌تواند ریسک پیاده‌سازی اشتباه را کاهش دهد.


# جمع‌بندی

در این مقاله یک جریان کامل ورود با Google در PHP را از دید معماری بررسی کردیم.

فرآیند کلی به این شکل است:


User

 ↓

Login with Google

 ↓

Authorization Request

 ↓

Google Login

 ↓

Authorization Code

 ↓

PHP Callback

 ↓

State Validation

 ↓

Code Exchange

 ↓

ID Token

 ↓

Signature + Claims Validation

 ↓

Find/Create User

 ↓

User Identity

 ↓

PHP Session

 ↓

Dashboard

مهم‌ترین نکته این است که OAuth 2.0 و OpenID Connect را فقط به‌عنوان چند URL و Token نبینیم.

یک پیاده‌سازی امن باید مواردی مانند:


State

Nonce

PKCE

HTTPS

Token Validation

Session Security

Identity Mapping

را نیز در نظر بگیرد.

با این معماری، Login با Google می‌تواند به شکل تمیز و قابل توسعه در یک پروژه PHP پیاده‌سازی شود و بعداً Providerهای دیگری مانند GitHub یا Apple نیز به سیستم Identity اضافه شوند.

مقاله بعدی

در مقاله بعدی می‌توانیم یک قدم جلوتر برویم و سراغ موضوع مهم Refresh Token در PHP برویم؛ اینکه Refresh Token دقیقاً چیست، چرا نباید آن را مثل Access Token مدیریت کرد و چگونه می‌توان Token Rotation، Revocation و Expiration را به شکل امن در یک سیستم واقعی PHP پیاده‌سازی کرد.

نظرات (0)

نظرات پس از تأیید مدیر نمایش داده می‌شوند

هنوز نظری ثبت نشده است؛ اولین نفر باشید!

مقالات مرتبط

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

ساخت CMS با Laravel 12؛ آموزش ساخت سیستم مدیریت محتوا از صفر

۲۴ دقیقه
ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

ساخت فروشگاه اینترنتی با Laravel 12؛ آموزش پروژه‌محور از صفر تا طراحی هسته فروشگاه

۲۲ دقیقه
ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

ساخت Marketplace با Laravel؛ آموزش طراحی فروشگاه چندفروشندگی

۲۲ دقیقه
مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

مدیریت Queueهای سنگین در Laravel؛ آموزش پردازش کارهای طولانی

۲۲ دقیقه