مقدمه
امروزه بسیاری از وبسایتها بهجای اینکه کاربر را مجبور کنند برای سایت یک رمز عبور جدید ایجاد کند، امکان ورود با حسابهای سرویسهایی مانند 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 پیادهسازی کرد.





