ورود یکپارچه (SSO)
سوپر اپلیکیشن مهراد
این سند نحوهٔ ورود کاربران مهراد به سایت یا اپلیکیشن پذیرنده و دریافت اطلاعات پایهٔ آنها را توضیح میدهد. ساختار بر پایهٔ OAuth2 و OpenID Connect است و تبادل token به صورت سرور-به-سرور و با امضای HMAC-SHA256 انجام میشود.
نحوهٔ عملکرد ورود یکپارچه
نقطهٔ شروع این جریان اپلیکیشن مهراد است، نه سایت پذیرنده:
- کاربر در اپلیکیشن مهراد وارد حساب خود میشود و سرویس و فروشگاه پذیرنده را انتخاب میکند.
- مهراد یک کد موقت (Authorization Code) میسازد و کاربر را به آدرس بازگشتی که هنگام راهاندازی ثبت شده است هدایت میکند. کد داخل همین آدرس قرار میگیرد.
- سرور پذیرنده کد را دریافت میکند و آن را در endpoint دریافت token با Access Token و ID Token تعویض میکند.
- با Access Token، اطلاعات مجاز کاربر (پروفایل، آدرس، موجودی) قابل دریافت است و کاربر در سایت پذیرنده وارد میشود.
بنابراین آنچه پذیرنده پیادهسازی میکند، مرحلهٔ ۲ به بعد است: مسیری در سایت پذیرنده که کد را دریافت و پردازش میکند.
نکات کلیدی
- آدرس بازگشت پذیرنده یک الگو است که هنگام راهاندازی ثبت میشود و مهراد کد را داخل آن قرار میدهد. شکل نهایی آدرس را مهراد میسازد و کاربر را به همان هدایت میکند.
- کد موقت ۳۰۰ ثانیه (۵ دقیقه) اعتبار دارد.
- در نسخهٔ فعلی API، parameter
stateوجود ندارد و مهراد هیچ parameter دیگری به آدرس بازگشت پذیرنده اضافه نمیکند. آنچه به سایت پذیرنده میرسد، همان الگوی ثبتشده به همراه کد است. - کاربر با یک ناوبری معمولی در همان صفحه به سایت پذیرنده منتقل میشود؛ این صفحه یک صفحهٔ وب عادی است و محدودیت خاصی ندارد. مسیر بازگشت خودکار به اپلیکیشن وجود ندارد.
پیشنیازها
پیش از شروع، این موارد باید برای پذیرنده تنظیم شده باشد:
| مورد | توضیح |
|---|---|
merchant_id | ID پذیرنده |
client_secret | کلید محرمانه برای امضای request دریافت token |
| آدرس بازگشت (Redirect URI) | آدرسی در سایت پذیرنده که کد موقت به آن تحویل داده میشود |
| Scopeهای فعال | مشخص میکند به کدام اطلاعات کاربر دسترسی وجود دارد |
این مقادیر هنگام راهاندازی در اختیار پذیرنده قرار میگیرد. برای هر ترکیب پذیرنده و سرویس، تنظیمات جداگانهای وجود دارد.
فهرست endpointها
| endpoint | کاربرد | authentication |
|---|---|---|
POST /gateway/sso/token | تبدیل کد موقت به token | بدون header؛ با کد موقت و client_signature |
POST /gateway/sso/introspect | بررسی وضعیت یک token | در نسخهٔ فعلی بدون authentication |
GET /gateway/sso/.well-known/openid-configuration | سند Discovery | عمومی |
GET /gateway/sso/.well-known/jwks.json | کلیدهای عمومی برای اعتبارسنجی ID Token | عمومی |
GET /gateway/sso/profile | اطلاعات پروفایل کاربر | Access Token + scope profile |
GET /gateway/sso/address | آدرس کاربر | Access Token + scope address |
GET /gateway/sso/balance | موجودی قابل استفادهٔ کاربر | Access Token + scope balance |
مرحله ۱ — دریافت کد موقت
کاربر از اپلیکیشن مهراد به آدرس بازگشت پذیرنده هدایت میشود و کد موقت در همان آدرس قرار دارد. اقداماتی که سایت پذیرنده باید انجام دهد:
- کد از آدرس خوانده میشود.
- کد باید بلافاصله از سمت سرور به endpoint دریافت token ارسال شود (مرحلهٔ ۲). کد فقط ۵ دقیقه اعتبار دارد.
- تا پیش از دریافت موفق token، کاربر نباید وارد شده در نظر گرفته شود.
مرحله ۲ — تبدیل کد به token
/gateway/sso/tokenAuthentication: این request به authentication از طریق header نیاز ندارد؛ اصالت آن با client_signature بررسی میشود.
ساخت client_signature
امضا یک HMAC-SHA256 است که روی خودِ کد موقت و با کلید client_secret پذیرنده محاسبه و به صورت hex ارسال میشود.
const crypto = require("crypto");
function clientSignature(clientSecret, authorizationCode) {
return crypto
.createHmac("sha256", clientSecret)
.update(authorizationCode)
.digest("hex");
}import hashlib
import hmac
def client_signature(client_secret: str, authorization_code: str) -> str:
return hmac.new(
key=client_secret.encode("utf-8"),
msg=authorization_code.encode("utf-8"),
digestmod=hashlib.sha256,
).hexdigest()اگر بیش از یک کلید فعال برای پذیرنده ثبت شده باشد، امضای تولیدشده با هر کدام از آنها پذیرفته میشود. این ویژگی برای تعویض کلید بدون قطعی سرویس در نظر گرفته شده است: ابتدا کلید جدید فعال میشود، سرویس به آن منتقل میشود و سپس کلید قدیمی غیرفعال میشود.
ورودی
grant_ typestringالزامیmerchant_ iduuidالزامیcodestringالزامیclient_ signaturestringالزامی{
"grant_type": "authorization_code",
"merchant_id": "6c6a2d61-63f1-44a9-9a5e-2b2d9a51f0c7",
"code": "kGf3t0Zq2m9N1sVb7pXcQwErTyUiOpAsDfGhJkLzXcV",
"client_signature": "9f1c2d3e4b5a60718293a4b5c6d7e8f90112233445566778899aabbccddeeff0"
}Response
access_ tokenjwttoken_ typestringexpires_ inintegerscopestringid_ tokenjwtاختیاریresponse این endpoint بدون پوشش استاندارد بازگردانده میشود:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 43200,
"scope": "openid profile address balance",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}پردازش response
access_tokenباید روی سرور نگهداری و در مرحلهٔ ۳ استفاده شود. اعتبار آن ۴۳۲۰۰ ثانیه (۱۲ ساعت) است و Refresh Token صادر نمیشود؛ پس از انقضا کاربر باید دوباره از اپلیکیشن مهراد وارد شود.scopeباید بررسی شود و فقط endpointهایی فراخوانی شوند که دسترسیشان اعطا شده است.id_tokenهویت کاربر است؛ پیش از اعتماد به آن، باید امضایش بررسی شود (مرحلهٔ ۴).
نکتهٔ زمانبندی: مدت اعتبار این token با token اپلیکیشن پذیرنده (۲ ساعت) متفاوت است. جزئیات آن در مستندات ارتباط پذیرندگان آمده است.
مرحله ۳ — دریافت اطلاعات کاربر
هر سه endpoint زیر با Access Token در header Authorization فراخوانی میشوند و هرکدام به scope مخصوص خود نیاز دارند. اگر token scope لازم را نداشته باشد، response آن با کد ۴۰۳ و خطای SSO_INSUFFICIENT_SCOPE برگردانده میشود.
Authorization: Bearer <access_token>پروفایل کاربر
/gateway/sso/profilescope لازم: profile
{
"sub": "3f1a9c22-58b4-4f6e-9d77-1b2c3d4e5f60",
"name": "زهرا محمدی",
"first_name": "زهرا",
"last_name": "محمدی",
"national_code": "0012345678",
"mobile": "09121234567",
"birth_date": "1990-05-14"
}مقدار sub، ID کاربر در مهراد است و در تمام endpointها ثابت میماند؛ برای اتصال حساب مهراد به حساب کاربر در سیستم پذیرنده باید از همین مقدار استفاده شود، نه از شمارهٔ موبایل یا کد ملی.
birth_date به قالب YYYY-MM-DD (تاریخ میلادی) برگردانده میشود. به جز sub، بقیهٔ فیلدها ممکن است null باشند؛ پیادهسازی باید این حالت را بپذیرد.
آدرس کاربر
/gateway/sso/addressscope لازم: address
{
"sub": "3f1a9c22-58b4-4f6e-9d77-1b2c3d4e5f60",
"address": "تهران، خیابان ولیعصر، کوچهٔ بهار، پلاک ۱۲",
"province": "تهران",
"city": "تهران",
"postal_code": "1234567890"
}اگر آدرسی برای کاربر ثبت نشده باشد، فیلدها null برمیگردند.
موجودی کاربر
/gateway/sso/balancescope لازم: balance
{
"sub": "3f1a9c22-58b4-4f6e-9d77-1b2c3d4e5f60",
"available_balance": 12500000
}available_balance مجموع دارایی قابل استفادهٔ کاربر برای همان سرویسی است که token برای آن صادر شده و به ریال است. این عدد لحظهای است؛ نباید ذخیره و دوباره استفاده شود و پیش از نمایش سبد خرید باید مجدداً دریافت شود.
مرحله ۴ — اعتبارسنجی ID Token
id_token با الگوریتم RS256 امضا میشود و کلید عمومی آن از JWKS قابل دریافت است:
/gateway/sso/.well-known/jwks.jsonنمونهٔ محتوای ID Token:
{
"iss": "https://api.mehrad.app",
"sub": "3f1a9c22-58b4-4f6e-9d77-1b2c3d4e5f60",
"aud": "6c6a2d61-63f1-44a9-9a5e-2b2d9a51f0c7",
"token_type": "sso_id",
"name": "زهرا محمدی",
"first_name": "زهرا",
"last_name": "محمدی",
"address": "تهران، خیابان ولیعصر، کوچهٔ بهار، پلاک ۱۲",
"mobile": "09121234567",
"exp": 1790000000,
"iat": 1789992800,
"jti": "0a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9"
}پیش از اعتماد به محتوای آن باید بررسی شود که: امضا با کلید عمومی معتبر است، iss برابر آدرس سرویس است، aud برابر merchant_id پذیرنده است و exp نگذشته باشد. مدت اعتبار ID Token کوتاهتر از Access Token است و برای authentication کاربر در لحظهٔ ورود در نظر گرفته شده، نه برای نگهداری بلندمدت.
بررسی وضعیت token (Introspection)
/gateway/sso/introspectدر نسخهٔ فعلی، برای این endpoint هیچ header مربوط به authentication یا امضایی لازم نیست و request تنها شامل دو فیلد زیر است:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"merchant_id": "6c6a2d61-63f1-44a9-9a5e-2b2d9a51f0c7"
}response برای token معتبر:
{
"active": true,
"scope": "openid profile address balance",
"merchant_id": "6c6a2d61-63f1-44a9-9a5e-2b2d9a51f0c7",
"service_id": "2f1b7d6e-2f4e-4a1a-9c22-6f0c0a1d5b31",
"sub": "3f1a9c22-58b4-4f6e-9d77-1b2c3d4e5f60",
"exp": 1790000000,
"iat": 1789956800
}برای token نامعتبر یا منقضی:
{
"active": false
}اگر token برای پذیرندهٔ دیگری صادر شده باشد، response آن با کد ۴۰۱ و خطای SSO_UNAUTHORIZED_CLIENT برگردانده میشود.
این endpoint برای بررسی اعتبار tokenهای در اختیار پذیرنده به کار میرود.
سند Discovery
/gateway/sso/.well-known/openid-configurationاین سند آدرس endpointها، الگوریتم امضای ID Token و فهرست scopeهای اعلامشده را برمیگرداند و عمومی است.
Scopeها
فهرست scopeهایی که سند Discovery اعلام میکند:
| Scope | دسترسی |
|---|---|
openid | صدور ID Token و دسترسی به ID کاربر (sub) |
profile | نام، نام خانوادگی، کد ملی، موبایل و تاریخ تولد |
address | آدرس کاربر |
balance | موجودی قابل استفادهٔ کاربر |
order | ایجاد پرداخت سفارش برای کاربر؛ جزئیات در مستندات سفارش |
نکات مهم:
- scopeها هنگام راهاندازی برای ترکیب پذیرنده و سرویس تنظیم میشوند؛ در request دریافت token، مقداری برای انتخاب scope ارسال نمیشود.
- scopeهای اعطاشده در فیلد
scopeاز response دریافت token برگردانده میشوند و همین مقدار مبنای تصمیمگیری است.
فهرست خطاها
اگر request با خطا مواجه شود، response آن کدی غیر از ۲xx دارد و success در آن false است. کد خطا در فیلد error.code و متن قابل نمایش در error.message برگردانده میشود. ساختار کامل خطا در بخش مقدمه آمده است. endpointهای Discovery و JWKS خطای اختصاصی ندارند.
تبدیل کد به token
/gateway/sso/token| کد خطا | HTTP | توضیح و اقدام پیشنهادی |
|---|---|---|
| VALIDATION_ERROR | 422 | یکی از فیلدهای grant_type، merchant_id یا client_signature ارسال نشده یا نوع آن نادرست است. |
| SSO_UNSUPPORTED_GRANT_TYPE | 400 | مقدار grant_type باید authorization_code باشد. |
| SSO_INVALID_REQUEST | 400 | code در body ارسال نشده است. |
| SSO_UNAUTHORIZED_CLIENT | 401 | امضای ارسالی با هیچیک از کلیدهای فعال پذیرنده همخوانی ندارد؛ باید client_secret و نحوهٔ ساخت امضا بررسی شود. |
| SSO_INVALID_GRANT | 400 | کد موقت منقضی شده، برای پذیرندهٔ دیگری صادر شده یا کاربر آن دیگر وجود ندارد؛ کاربر باید دوباره از اپلیکیشن وارد شود. |
{
"success": false,
"error": {
"code": "SSO_INVALID_GRANT",
"message": "این کد موقت دیگر معتبر نیست یا زمانش تمام شده است.",
"details": null
}
}بررسی وضعیت token
/gateway/sso/introspect| کد خطا | HTTP | توضیح و اقدام پیشنهادی |
|---|---|---|
| VALIDATION_ERROR | 422 | token یا merchant_id ارسال نشده است. |
| SSO_UNAUTHORIZED_CLIENT | 401 | token برای پذیرندهٔ دیگری صادر شده است. |
{
"success": false,
"error": {
"code": "SSO_UNAUTHORIZED_CLIENT",
"message": "امضای امنیتی درخواست با هیچیک از کلیدهای ثبتشده برای این پذیرنده همخوانی ندارد.",
"details": null
}
}اطلاعات کاربر
/gateway/sso/profile/gateway/sso/address/gateway/sso/balance| کد خطا | HTTP | توضیح و اقدام پیشنهادی |
|---|---|---|
| HTTP_EXCEPTION | 401 | header Authorization ارسال نشده یا قالب Bearer <token> ندارد. |
| SSO_INVALID_ACCESS_TOKEN | 401 | Access Token نامعتبر یا منقضی است؛ کاربر باید دوباره وارد شود. |
| SSO_INSUFFICIENT_SCOPE | 403 | token، scope لازم برای این endpoint را ندارد. نام scope لازم در details.required_scope آمده است. |
| USER_NOT_FOUND | 404 | کاربر صاحب token دیگر در مهراد وجود ندارد (فقط در profile و balance). |
{
"success": false,
"error": {
"code": "SSO_INSUFFICIENT_SCOPE",
"message": "شما به این بخش دسترسی ندارید.",
"details": {
"required_scope": "balance"
}
}
}نکات امنیتی و عملیاتی
client_secretو tokenها فقط باید روی سرور نگهداری شوند؛ نباید در کد سمت مرورگر یا اپلیکیشن موبایل قرار گیرند.- تبادل کد با token باید سرور-به-سرور انجام شود.
- کد موقت عمر کوتاهی دارد؛ نباید لاگ شود و باید بلافاصله مصرف شود.
- مقادیر
expوiattokenها باید همواره بررسی شوند. - کاربر باید با
subبه حساب داخلی پذیرنده متصل شود.
چکلیست پیادهسازی
- دریافت
merchant_id،client_secretو ثبت آدرس بازگشت - پیادهسازی مسیر دریافت کد موقت در سایت
- ساخت
client_signatureبه روش HMAC-SHA256 روی کد موقت - فراخوانی
POST /gateway/sso/tokenاز سمت سرور و مدیریت خطاهای آن - اعتبارسنجی امضا،
iss،audوexpدرid_token - اتصال کاربر به حساب داخلی بر اساس
sub - خواندن
scoperesponse و فراخوانی endpointهای مجاز - مدیریت انقضای Access Token پس از ۱۲ ساعت و نبود Refresh Token
- نگهداری امن کلید و tokenها روی سرور