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

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

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

Account Linking در OpenID Connect با PHP؛ اتصال امن حساب کاربران به Identity Provider

ممدیر کل سیستم ۳۱ شهریور ۱۴۰۵ ۲۰ دقیقه مطالعه ۱ بازدید
Account Linking در OpenID Connect با PHP؛ اتصال امن حساب کاربران به Identity Provider

مقدمه

فرض کنید یک سایت 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 به شکل قابل توسعه‌تری پیاده‌سازی کنیم.

نظرات (0)

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

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

مقالات مرتبط

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

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

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

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

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

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

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

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

۲۲ دقیقه