مقدمه
فرض کنید یک سایت PHP داریم که کاربران قبلاً در آن حساب ساختهاند.
مثلاً:
User ID: 125
Email: ali@example.com
Password: ********
بعداً تصمیم میگیریم ورود با OpenID Connect را نیز به سایت اضافه کنیم.
کاربر با Identity Provider وارد میشود و Identity Provider اطلاعاتی مانند:
issuer
subject
email
name
email_verified
را در اختیار Application قرار میدهد.
حالا یک سؤال مهم داریم:
چطور متوجه شویم این کاربر OIDC همان کاربری است که قبلاً در دیتابیس سایت ما حساب داشته است؟
اگر این مرحله بهدرستی طراحی نشود، ممکن است یک کاربر چند حساب مختلف داشته باشد:
Local Account
+
OIDC Account
↓
Duplicate Users
یا حتی بدتر، در صورت طراحی اشتباه، حساب یک کاربر به حساب کاربر دیگری متصل شود.
راهحل این مسئله Account Linking یا اتصال هویت خارجی به حساب داخلی است.
در این مقاله با PHP بررسی میکنیم که Account Linking در OpenID Connect چگونه کار میکند، چرا issuer + subject اهمیت دارد، چه زمانی میتوان از Email استفاده کرد، چگونه از Duplicate Account جلوگیری کنیم و چگونه فرآیند Linking را به شکل امن طراحی کنیم.
# Account Linking چیست؟
Account Linking یعنی اتصال یک Identity خارجی به یک حساب داخلی Application.
مثلاً کاربر قبلاً در سایت ما حساب دارد:
Local User
ID = 125
و سپس با OpenID Connect وارد میشود:
OIDC Identity
issuer = https://auth.example.com
subject = 987654
ما این دو را به یکدیگر متصل میکنیم:
Local User
ID 125
│
┌────────┴────────┐
│ │
Password OIDC Identity
│
issuer + subject
از این به بعد Application میداند که این Identity OIDC متعلق به User شماره 125 است.
# چرا Account Linking مهم است؟
بدون Account Linking ممکن است چنین وضعیتی ایجاد شود:
User 125
email = ali@example.com
و بعد کاربر با OIDC وارد شود و سیستم به اشتباه User جدیدی بسازد:
User 340
email = ali@example.com
در نتیجه:
ali@example.com
│
├── Local Account 125
│
└── OIDC Account 340
این وضعیت میتواند مشکلات زیادی ایجاد کند:
- سفارشها در حساب اشتباه قرار میگیرند
- تنظیمات کاربر جدا میشوند
- نقشها و Permissionها متفاوت میشوند
- سابقه فعالیت از بین میرود
- دادههای کاربر دوپاره میشوند
- فرآیند Login پیچیده میشود
بنابراین Identity Mapping باید از ابتدا درست طراحی شود.
# مهمترین شناسه در OpenID Connect
در OIDC معمولاً مهمترین شناسه پایدار کاربر:
issuer + subject
است.
یعنی:
iss
+
sub
فرض کنید ID Token چنین باشد:
{
"iss": "https://auth.example.com",
"sub": "123456789",
"email": "ali@example.com",
"email_verified": true
}
Identity اصلی کاربر:
https://auth.example.com + 123456789
است.
# چرا فقط Subject کافی نیست؟
فرض کنید دو Identity Provider داشته باشیم:
Provider A
sub = 12345
Provider B
sub = 12345
اگر فقط sub را ذخیره کنیم، ممکن است تصور کنیم هر دو یک کاربر هستند.
در حالی که:
Provider A + 12345
با:
Provider B + 12345
دو Identity متفاوت هستند.
بنابراین بهتر است ترکیب زیر را داشته باشیم:
issuer
subject
و روی این دو مقدار Constraint یکتا قرار دهیم.
# چرا Email جایگزین issuer + subject نیست؟
ممکن است وسوسه شویم که بگوییم:
if email exists:
connect account
اما این روش بهتنهایی امن نیست.
Email یک Attribute است، نه شناسه اصلی OIDC.
همچنین ممکن است:
- Email تغییر کند
- Provider آن را با سیاست متفاوتی مدیریت کند
- Email در Token تأیید نشده باشد
- دو Provider یک Email مشابه داشته باشند
- یک Email در زمان دیگری به حساب دیگری اختصاص پیدا کند
بنابراین:
issuer + subject
باید Identity اصلی خارجی باشد.
# email_verified چیست؟
یکی از Claimهای مهم:
email_verified
است.
مثلاً:
{
"email": "ali@example.com",
"email_verified": true
}
یعنی Provider اعلام میکند Email مربوط به این Identity تأیید شده است.
اما باید توجه کنیم که:
تأیید Email با اثبات مالکیت یک حساب داخلی موجود یکسان نیست.
این تفاوت در Account Linking اهمیت زیادی دارد.
# سناریوی اول: کاربر قبلاً حساب ندارد
فرض کنیم کاربر برای اولین بار با OIDC وارد سایت میشود.
Token:
{
"iss": "https://auth.example.com",
"sub": "987654",
"email": "ali@example.com",
"email_verified": true,
"name": "Ali"
}
Application ابتدا بررسی میکند:
issuer + subject
در جدول Identity وجود دارد یا خیر.
اگر وجود نداشته باشد، میتواند بر اساس سیاست سیستم:
Create Local User
+
Create OIDC Identity
انجام دهد.
ساختار:
users
│
└── user_id = 125
user_identities
│
├── user_id = 125
├── issuer = https://auth.example.com
└── subject = 987654
# سناریوی دوم: Identity قبلاً متصل شده است
فرض کنیم کاربر قبلاً Login کرده است.
جدول:
user_id_identities
دارای:
user_id = 125
issuer = https://auth.example.com
subject = 987654
دفعه بعد که کاربر Login میکند:
OIDC Identity
↓
issuer + subject
↓
Find Identity
↓
User 125
↓
Create Session
دیگر حساب جدیدی ساخته نمیشود.
# سناریوی سوم: کاربر حساب داخلی دارد ولی OIDC متصل نیست
این مهمترین حالت Account Linking است.
کاربر قبلاً دارد:
User ID = 125
Email = ali@example.com
اما:
issuer + subject
در سیستم وجود ندارد.
کاربر با OIDC Login میکند.
سیستم متوجه میشود:
OIDC Identity = New
حالا نباید فوراً بگوییم:
Same Email
↓
Automatically Link
بلکه بهتر است فرآیند Linking کنترلشده باشد.
# فرآیند پیشنهادی Account Linking
یک فرآیند امن میتواند چنین باشد:
OIDC Login
↓
Identity exists?
/ \
Yes No
| |
Login Check existing account
↓
Account found?
/ \
Yes No
| |
Ask to link Create account
|
Re-authenticate
|
Confirm linking
|
Create identity link
# چرا Re-authentication مهم است؟
فرض کنید کاربر همین حالا در سایت Login است.
مهاجم somehow بتواند فرآیند Linking را بهصورت ناخواسته اجرا کند.
اگر سیستم بدون تأیید مجدد اجازه اتصال Identity را بدهد، ممکن است یک Identity خارجی به حساب کاربر متصل شود.
برای عملیات حساس بهتر است:
Current Session
↓
Re-authentication
↓
Account Linking
انجام شود.
# طراحی جدول user_identities
یک جدول مناسب میتواند چنین باشد:
CREATE TABLE user_identities (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
issuer VARCHAR(255) NOT NULL,
subject VARCHAR(255) NOT NULL,
provider_name VARCHAR(100) NULL,
email_at_link VARCHAR(255) NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
UNIQUE KEY unique_oidc_identity (
issuer,
subject
),
INDEX idx_user_id (user_id)
);
قسمت بسیار مهم:
UNIQUE KEY unique_oidc_identity (
issuer,
subject
)
است.
این Constraint مانع میشود یک Identity خارجی بهصورت همزمان به چند User داخلی متصل شود.
# آیا Email را هم ذخیره کنیم؟
میتوانیم Email زمان Linking را ذخیره کنیم:
email_at_link
اما باید بدانیم این مقدار صرفاً یک Attribute تاریخی یا کمکی است.
Identity اصلی:
issuer + subject
است.
بنابراین اگر کاربر Email خود را در Provider تغییر دهد، نباید صرفاً بر اساس Email جدید، User دیگری ایجاد کنیم.
# مدل داده پیشنهادی
ساختار کلی:
users
│
├── id
├── email
├── name
└── ...
user_identities
│
├── id
├── user_id
├── issuer
├── subject
├── provider_name
├── email_at_link
└── ...
ارتباط:
User
│
├── Local Password
│
├── OIDC Identity A
│
└── OIDC Identity B
این مدل امکان اتصال چند Identity به یک حساب داخلی را نیز فراهم میکند.
# اتصال چند Identity به یک حساب
فرض کنید کاربر میتواند با دو Provider وارد شود:
Google
Company SSO
ساختار:
User 125
│
┌─────────┴─────────┐
│ │
Google Identity Company Identity
│ │
iss + sub iss + sub
در این حالت User داخلی یکی است اما چند Identity خارجی دارد.
# پیدا کردن Identity در PHP
بعد از اعتبارسنجی ID Token:
$issuer = $claims['iss'];
$subject = $claims['sub'];
سپس:
$stmt = $pdo->prepare("
SELECT user_id
FROM user_identities
WHERE issuer = ?
AND subject = ?
LIMIT 1
");
$stmt->execute([
$issuer,
$subject
]);
$identity = $stmt->fetch();
اگر نتیجه وجود داشته باشد:
$userId = $identity['user_id'];
و Login انجام میشود.
# اگر Identity وجود نداشت چه کنیم؟
ابتدا بررسی میکنیم آیا کاربر داخلی پیدا میشود یا خیر.
مثلاً:
$stmt = $pdo->prepare("
SELECT id
FROM users
WHERE email = ?
LIMIT 1
");
$stmt->execute([
$claims['email']
]);
$user = $stmt->fetch();
اگر User وجود داشته باشد، نباید بلافاصله Linking انجام دهیم.
بهتر است کاربر را وارد فرآیند تأیید اتصال کنیم.
# چرا Automatic Linking میتواند خطرناک باشد؟
فرض کنید سیستم چنین کاری کند:
if ($userByEmail) {
linkIdentity($userByEmail->id);
}
ممکن است در برخی شرایط خطرناک باشد.
برای مثال، Email موجود در Token ممکن است:
email_verified = false
باشد.
در این حالت سیستم نباید صرفاً بر اساس Email حساب داخلی را به Identity خارجی متصل کند.
# یک سیاست بهتر
میتوانیم این قوانین را داشته باشیم:
Identity exists
↓
Login
Identity does not exist
↓
Existing local account?
↓
Yes
↓
Require account verification
↓
Require re-authentication
↓
Confirm linking
↓
Create identity link
این فرآیند نسبت به Automatic Linking کنترل بیشتری ایجاد میکند.
# Account Linking از داخل پروفایل
یک روش دیگر این است که کاربر خودش از داخل حسابش Identity را متصل کند.
مثلاً:
Account Settings
↓
Connected Accounts
و:
Google Not Connected
Company SSO Connected
کاربر روی:
Connect Google
کلیک میکند.
سپس:
Redirect to Provider
↓
Authentication
↓
Callback
↓
Verify Identity
↓
Link to Current User
این روش از نظر UX نیز قابل فهم است.
# جلوگیری از اتصال Identity متعلق به User دیگر
این مورد بسیار مهم است.
فرض کنید:
OIDC Identity A
قبلاً به:
User 100
متصل شده است.
کاربر:
User 200
نباید بتواند همان Identity را به حساب خودش متصل کند.
قبل از Insert:
$stmt = $pdo->prepare("
SELECT user_id
FROM user_identities
WHERE issuer = ?
AND subject = ?
LIMIT 1
");
$stmt->execute([
$issuer,
$subject
]);
$existing = $stmt->fetch();
if ($existing) {
throw new RuntimeException(
'This identity is already linked.'
);
}
و مهمتر از آن، Database باید Constraint یکتا داشته باشد.
# چرا فقط بررسی PHP کافی نیست؟
فرض کنید دو درخواست همزمان ارسال شوند:
Request A
↓
Check Identity
↓
Not Found
Request B
↓
Check Identity
↓
Not Found
هر دو تصور میکنند Identity آزاد است.
اگر فقط در PHP بررسی کنیم، ممکن است هر دو Insert شوند.
اما اگر Database داشته باشد:
UNIQUE (issuer, subject)
یکی از آنها شکست خواهد خورد.
این یک مثال مهم از Defense in Depth است.
# Transaction در Account Linking
فرآیند Linking بهتر است در Transaction انجام شود.
مثلاً:
$pdo->beginTransaction();
try {
// Verify current user
// Verify OIDC identity
// Check identity ownership
// Insert identity link
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}
این کار باعث میشود عملیات چندمرحلهای به شکل کنترلشده انجام شود.
# Account Linking و CSRF
اگر Linking از داخل حساب کاربر انجام میشود، باید درخواست را در برابر CSRF محافظت کنیم.
مثلاً:
<form method="POST" action="/account/link">
<input type="hidden"
name="csrf_token"
value="<?= htmlspecialchars($csrfToken) ?>">
<button type="submit">
اتصال حساب
</button>
</form>
در سمت سرور:
if (!hash_equals(
$_SESSION['csrf_token'] ?? '',
$_POST['csrf_token'] ?? ''
)) {
http_response_code(419);
exit('Invalid CSRF token.');
}
Account Linking یک عملیات حساس است و نباید فقط به ظاهر صفحه یا وجود Session اعتماد کنیم.
# Re-authentication قبل از Linking
میتوانیم قبل از اتصال Identity از کاربر بخواهیم رمز عبور فعلی را دوباره وارد کند.
مثلاً:
Password
[ ******** ]
[ Continue ]
یا اگر سیستم Passwordless است، از یک روش Authentication مجدد استفاده کنیم.
هدف:
Current Session
+
Fresh Authentication
=
Allow Sensitive Linking
است.
# Login با OIDC بعد از Linking
بعد از اینکه Identity متصل شد، Login بسیار سادهتر میشود:
OIDC Login
↓
Validate ID Token
↓
issuer + subject
↓
Find user_id
↓
session_regenerate_id()
↓
Create Local Session
مثلاً:
session_regenerate_id(true);
$_SESSION['user_id'] = $userId;
$_SESSION['authenticated'] = true;
# Account Unlinking چیست؟
در کنار Linking باید مفهوم Unlinking را نیز در نظر بگیریم.
یعنی کاربر بخواهد Identity متصلشده را حذف کند.
مثلاً:
Connected Accounts
Google
[ Disconnect ]
با کلیک روی Disconnect:
OIDC Identity
↓
Remove Link
اما این عملیات نیز حساس است.
# چرا Unlinking میتواند خطرناک باشد؟
فرض کنید کاربر فقط یک روش Login داشته باشد:
OIDC
اگر آن را حذف کند، دیگر ممکن است نتواند وارد حساب شود.
بنابراین قبل از Unlink باید بررسی کنیم:
Does user have another authentication method?
مثلاً:
Password
+
Google
اگر Google حذف شود، Password هنوز وجود دارد.
اما اگر:
Google
تنها روش ورود باشد، باید قبل از Unlink یک روش Authentication دیگر ایجاد یا تأیید شود.
# منطق Unlink
مثلاً:
User
│
├── Password
└── Google
Google قابل حذف است.
اما:
User
│
└── Google
ممکن است اجازه حذف مستقیم Google را ندهیم.
میتوانیم ابتدا از کاربر بخواهیم:
Set Password
یا یک روش Authentication معتبر دیگر اضافه کند.
# Account Linking و Email Verification
فرض کنیم Token چنین باشد:
{
"email": "ali@example.com",
"email_verified": true
}
این اطلاعات برای برخی تصمیمهای Linking مفید است.
اما باید بین این موارد تفاوت بگذاریم:
Identity Verified by Provider
و:
Existing Local Account Ownership Verified
این دو الزاماً یک مفهوم نیستند.
برای اتصال به یک حساب موجود، بهتر است Proof لازم برای کنترل حساب داخلی نیز داشته باشیم.
# Account Linking با Google
فرض کنید کاربر از Google وارد میشود.
پس از اعتبارسنجی ID Token:
iss
sub
email
email_verified
name
را داریم.
Application باید هویت Google را به شکل پایدار ذخیره کند:
issuer
subject
نه اینکه صرفاً بنویسد:
provider = google
email = user@example.com
# چرا Email ممکن است تغییر کند؟
کاربر ممکن است اطلاعات حساب Provider را تغییر دهد.
اگر Application به Email بهعنوان Primary Identity وابسته باشد، ممکن است Mapping خراب شود.
اما:
issuer + subject
برای شناسایی Identity مناسبتر است.
# UserInfo Endpoint
در OIDC ممکن است اطلاعات بیشتری از UserInfo Endpoint دریافت شود.
مثلاً:
GET /userinfo
Authorization: Bearer ACCESS_TOKEN
و پاسخ:
{
"sub": "987654",
"name": "Ali",
"email": "ali@example.com",
"email_verified": true
}
اما sub موجود در UserInfo باید با Subject مورد انتظار همان Identity سازگار باشد.
نباید اطلاعات UserInfo را بدون اعتبارسنجی مناسب وارد سیستم کنیم.
# Identity Mapping را از User Profile جدا کنیم
یک معماری تمیز بهتر است این دو را جدا کند:
User
↓
Profile Data
User Identity
↓
External Authentication Identity
مثلاً:
users
id = 125
name = Ali
phone = ...
...
user_identities
user_id = 125
issuer = ...
subject = ...
این جداسازی در آینده برای چند Provider بسیار مفید است.
# چند Provider برای یک User
فرض کنیم:
User 125
سه Identity دارد:
Google
Company SSO
GitHub
جدول:
user_id | issuer | subject
--------|-------------------------|--------
125 | https://accounts... | 123
125 | https://auth.company... | 987
125 | https://github.com/... | 555
همه این Identityها به User داخلی شماره 125 متصل هستند.
# Account Linking و نقشها
یک اشتباه مهم این است که Role را مستقیماً از Email یا Provider بگیریم.
مثلاً:
email = admin@example.com
→ role = admin
این طراحی مناسبی نیست.
Role و Permission باید در سیستم Authorization شما مدیریت شوند.
مثلاً:
users
roles
permissions
user_roles
role_permissions
OIDC باید هویت را مشخص کند و سیستم داخلی شما باید Authorization را مدیریت کند، مگر اینکه معماری شما صراحتاً ادعاها و نقشهای Provider را بهصورت کنترلشده و قابل اعتماد مصرف کند.
# Account Linking و Session
بعد از Linking یا Login باید Session ID را تغییر دهیم:
session_regenerate_id(true);
سپس:
$_SESSION['user_id'] = $userId;
این کار برای جلوگیری از Session Fixation مهم است.
# جلوگیری از Account Enumeration
فرض کنید کاربر یک Email وارد میکند.
نباید در بعضی فرآیندهای حساس پاسخهایی بدهیم که به مهاجم اجازه دهند بفهمد:
این Email حساب دارد
این Email حساب ندارد
پیامهای عمومی و کنترلشده میتوانند اطلاعات کمتری افشا کنند.
این موضوع در Password Reset نیز اهمیت زیادی دارد.
# ثبت Audit Log
Account Linking و Unlinking عملیات حساسی هستند.
بهتر است Event مربوط به آنها ثبت شود.
مثلاً:
account.identity.linked
account.identity.unlinked
با اطلاعاتی مانند:
user_id
issuer
provider
timestamp
request_id
اما نباید Token یا Secret را در Log ذخیره کنیم.
# نمونه Service در PHP
میتوانیم منطق Linking را در Service جداگانه قرار دهیم:
class IdentityLinkService
{
public function link(
int $userId,
string $issuer,
string $subject
): void {
// Validate identity
// Check ownership
// Create link
}
}
و Controller فقط فرآیند HTTP را مدیریت کند.
این معماری از قرار گرفتن منطق امنیتی در فایلهای مختلف جلوگیری میکند.
# نمونه کاملتر برای Linking
یک نمونه آموزشی:
public function linkIdentity(
PDO $pdo,
int $userId,
string $issuer,
string $subject,
?string $email
): void {
$pdo->beginTransaction();
try {
$stmt = $pdo->prepare("
SELECT id, user_id
FROM user_identities
WHERE issuer = ?
AND subject = ?
LIMIT 1
");
$stmt->execute([
$issuer,
$subject
]);
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
if ($existing) {
if ((int) $existing['user_id'] !== $userId) {
throw new RuntimeException(
'Identity already belongs to another account.'
);
}
$pdo->commit();
return;
}
$stmt = $pdo->prepare("
INSERT INTO user_identities (
user_id,
issuer,
subject,
email_at_link,
created_at,
updated_at
)
VALUES (?, ?, ?, ?, NOW(), NOW())
");
$stmt->execute([
$userId,
$issuer,
$subject,
$email
]);
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}
}
در Production باید Exceptionهای مربوط به Unique Constraint، Race Condition و اعتبارسنجیهای امنیتی نیز بهصورت دقیق مدیریت شوند.
# Race Condition در Account Linking
این موضوع مهم است.
فرض کنید دو درخواست همزمان برای Linking یک Identity ارسال شود.
Request A
↓
Check
↓
Not Found
Request B
↓
Check
↓
Not Found
سپس هر دو:
INSERT
انجام دهند.
راهحل اصلی فقط Lock در PHP نیست.
Database باید Constraint داشته باشد:
UNIQUE (issuer, subject)
و Application باید خطای Duplicate را به شکل مناسب مدیریت کند.
# Account Linking و Logout
بعد از Linking، کاربر باید بتواند Identityهای متصلشده را مشاهده کند.
مثلاً:
Account
│
├── Password
│
├── Google
│
└── Company SSO
در Logout، حذف Session داخلی همچنان جدا از Identity Linking است.
یعنی:
Linked Identity
≠
Active Session
این تفاوت را باید در طراحی سیستم حفظ کنیم.
# معماری پیشنهادی نهایی
یک معماری تمیز میتواند چنین باشد:
Identity Provider
│
issuer + subject
│
▼
┌──────────────────┐
│ user_identities │
└────────┬─────────┘
│
▼
users
│
┌─────────────┼─────────────┐
▼ ▼ ▼
Profile Roles Sessions
در این معماری:
- Identity خارجی در
user_identities - اطلاعات داخلی در
users - Authorization در Role/Permissionها
- Session در Session Layer
مدیریت میشود.
# چکلیست Account Linking
قبل از Production این موارد را بررسی کنید:
[ ] issuer ذخیره میشود
[ ] subject ذخیره میشود
[ ] UNIQUE(issuer, subject)
[ ] Email بهعنوان Identity اصلی استفاده نمیشود
[ ] email_verified بررسی میشود
[ ] ID Token Signature بررسی میشود
[ ] issuer بررسی میشود
[ ] audience بررسی میشود
[ ] expiration بررسی میشود
[ ] nonce بررسی میشود
[ ] State بررسی میشود
[ ] Linking نیازمند Authorization مناسب است
[ ] عملیات حساس Re-authentication دارد
[ ] CSRF محافظت شده است
[ ] Duplicate Identity کنترل شده است
[ ] Race Condition با Database Constraint کنترل شده است
[ ] Unlink بررسی میکند کاربر روش Login دیگری دارد
[ ] Tokenها Log نمیشوند
[ ] Audit Log برای Linking/Unlinking وجود دارد
[ ] Session بعد از Authentication regenerate میشود
# سوالات متداول
آیا باید Email کاربر را با Email موجود در دیتابیس مقایسه کنیم؟
Email میتواند برای پیدا کردن حساب احتمالی یا شروع فرآیند Linking استفاده شود، اما نباید بهتنهایی مبنای اعتماد برای اتصال حساب قرار گیرد.
شناسه اصلی کاربر OIDC چیست؟
برای Mapping هویت خارجی معمولاً ترکیب issuer + subject استفاده میشود.
آیا Subject بهتنهایی کافی است؟
خیر. Subject در محدوده Identity Provider معنا دارد. بنابراین بهتر است همراه با Issuer ذخیره شود.
آیا میتوان چند Provider را به یک User متصل کرد؟
بله. طراحی user_identities بهصورت جدا از جدول users این امکان را فراهم میکند.
آیا بعد از Account Linking باید Session جدید ایجاد کنیم؟
پس از Authentication موفق، بهتر است Session ID با session_regenerate_id(true) تغییر کند و سپس Session کاربر ایجاد شود.
آیا میتوان حساب را فقط بر اساس Email بهصورت خودکار Link کرد؟
بهتر است چنین کاری بدون کنترلهای امنیتی و تأیید مالکیت حساب انجام نشود. بهخصوص زمانی که وضعیت email_verified یا سیاست اعتماد Provider مشخص نیست.
اگر Identity قبلاً به حساب دیگری متصل شده باشد چه کنیم؟
نباید آن را به User جدید متصل کنیم. باید عملیات متوقف شود و وضعیت بهصورت کنترلشده مدیریت شود.
آیا حذف Identity همان Logout است؟
خیر. حذف Link یک Identity از حساب با Logout Session دو مفهوم کاملاً متفاوت هستند.
# جمعبندی
Account Linking یکی از بخشهای مهم معماری OpenID Connect است که معمولاً زمانی اهمیت خود را نشان میدهد که یک سایت از Login سنتی به سمت OIDC، SSO یا چند Identity Provider حرکت میکند.
اصل مهم این است که:
User داخلی
را از:
External Identity
جدا نگه داریم.
شناسه خارجی نیز بهتر است بر اساس:
issuer + subject
مدیریت شود.
معماری پیشنهادی:
OIDC Identity
↓
Validate Token
↓
issuer + subject
↓
user_identities
↓
user_id
↓
users
↓
Local Session
همچنین باید بین Identity Mapping، Authentication، Authorization و Session Management تفاوت قائل شویم.
با این ساختار میتوانیم در آینده چند Provider را به یک حساب متصل کنیم، امکان Unlink کردن Identityها را فراهم کنیم، از ایجاد حسابهای تکراری جلوگیری کنیم و SSO را در چند Application به شکل قابل توسعهتری پیادهسازی کنیم.





