{
  "title": "Token Service",
  "slug": "team/backend/services/token-service",
  "url": "/docs/team/backend/services/token-service",
  "frontmatter": {
    "layout": "doc",
    "title": "Token Service",
    "description": "سرویس مدیریت صدور و اعتبارسنجی توکن‌های OAuth2/OIDC بر پایه Ory Hydra",
    "version": "3.3.0",
    "status": "FINAL",
    "author": "Backend Team",
    "owner": "Backend Team",
    "created_at": "2026-07-14",
    "updated_at": "2026-07-15",
    "tags": "",
    "reviewers": ""
  },
  "sections": [
    {
      "level": 1,
      "heading": "Token Service",
      "content": "**Token Service — مشخصات فنی نهایی**\n\n> **مستند معماری سرویس — نسخه نهایی (FINAL)**\n>\n> مرجع تصمیمات معماری: [`ADR-Backend-006`](../../backend/ADR/ADR-Backend-006) (تمام تصمیمات قطعی و بایگانی سناریوهای قدیمی در ADR ثبت شده‌اند).\n>\n> منبع اصلی مشخصات فنی: `nons-api/services/token-service/blueprint.md` (نسخه ۳.۳).\n\n---"
    },
    {
      "level": 2,
      "heading": "فهرست محتوا",
      "content": "1. [هدف و دامنه](#۱-هدف-و-دامنه)\n2. [مسئولیت‌ها](#۲-مسئولیتها)\n3. [خارج از مسئولیت‌ها](#۳-خارج-از-مسئولیتها)\n4. [اجزای معماری](#۴-اجزای-معماری)\n5. [جریان‌های اصلی](#۵-جریانهای-اصلی)\n6. [مدل داده و Claims](#۶-مدل-داده-و-claims)\n7. [امنیت](#۷-امنیت)\n8. [توپولوژی استقرار](#۸-توپولوژی-استقرار)\n9. [رویدادها](#۹-رویدادها)\n10. [فازهای پیاده‌سازی](#۱۰-فازهای-پیادهسازی)\n11. [سوالات باز باقی‌مانده](#۱۱-سوالات-باز-باقیمانده)\n\n---"
    },
    {
      "level": 2,
      "heading": "۱. هدف و دامنه",
      "content": "**Service Objective**\n\nسرویس `token-service` مسئول **صدور، ابطال و اعتبارسنجی توکن‌های API** (OAuth2 / OIDC) در پلتفرم NONS است. این سرویس لایه توکن را از لایه هویت/نشست (auth-service مبتنی بر Kratos) جدا می‌کند تا:\n\n- اعتبارسنجی توکن به‌صورت **stateless و محلی** توسط هر میکروسرویس انجام شود (بدون تماس همزمان با auth-service).\n- چند نوع کلاینت پشتیبانی شود: وب (SPA/SSR)، موبایل، و در آینده third-party."
    },
    {
      "level": 3,
      "heading": "در دامنه این سند",
      "content": "- صدور access token، refresh token، id token (Hydra)\n- ابطال توکن (revoke) و مدیریت نشست‌های توکنی\n- افشای کلیدهای عمومی از طریق JWKS endpoint\n- الگوی اعتبارسنجی JWT در میکروسرویس‌ها\n- مدل داده claims، چرخه عمر توکن، چرخش کلید (key rotation)\n- **login-consent-app** به عنوان واسط استاندارد Ory بین Hydra و Kratos (شامل endpoint `/token-hook` برای غنی‌سازی claims)"
    },
    {
      "level": 3,
      "heading": "خارج از دامنه این سند",
      "content": "- ثبت‌نام، لاگین، MFA، مدیریت نشست مرورگر (بر عهده auth-service / Kratos)\n- طراحی UI صفحات لاگین/ثبت‌نام\n- منطق کسب‌وکار میکروسرویس‌های داخلی\n- RBAC/ABAC تفصیلی در سطح هر سرویس\n\n---"
    },
    {
      "level": 2,
      "heading": "۲. مسئولیت‌ها",
      "content": "**Responsibilities**\n\n- میزبانی سرور OAuth2/OIDC (ORY Hydra) جهت صدور access/refresh/id token.\n- اجرای جریان Login & Consent بین Hydra و Kratos از طریق `login-consent-app` (الگوی استاندارد Ory).\n- غنی‌سازی claims قبل از صدور توکن از طریق Hydra OAuth2 Token Hook.\n- افشای نقاط پایانی عمومی شامل JWKS (`/.well-known/jwks.json`) و OIDC discovery.\n- ابطال توکن‌ها (refresh token rotation، revoke، logout سمت سرور با **ترتیب صحیح**: ابتدا Hydra سپس Kratos).\n- نگهداری دیتابیس اختصاصی (جدا از Kratos) برای client، consent و grants.\n- انتشار رویدادهای حیات‌نامه توکن (صدور / ابطال) جهت audit trail.\n\n---"
    },
    {
      "level": 2,
      "heading": "۳. خارج از مسئولیت‌ها",
      "content": "**Non-Responsibilities**\n\n- **مدیریت هویت و نشست مرورگر:** بر عهده `auth-service` (ORY Kratos) است. token-service صرفاً توکن صادر می‌کند؛ تأیید اینکه «کاربر واقعاً لاگین کرده» از طریق `login-consent-app` و استعلام `GET /sessions/whoami` در Kratos انجام می‌شود.\n- **مدیریت نقش‌ها و دسترسی‌ها (Authorization):** بر عهده `iam-service` است. claims حساس (نقش، سطح دسترسی) از طریق **Hydra OAuth2 Token Hook** اضافه می‌شوند.\n- **رندر UI:** هیچ صفحه رابط کاربری در token-service رندر نمی‌شود؛ تمام تعاملات کلاینت از طریق پروتکل OAuth2 انجام می‌گیرد.\n\n---"
    },
    {
      "level": 2,
      "heading": "۴. اجزای معماری",
      "content": "**Architecture Components**\n\n| مؤلفه | نقش | فناوری |\n|---|---|---|\n| `auth-service` | مدیریت هویت، ثبت‌نام، لاگین، MFA، نشست مرورگر | ORY Kratos + Go wrapper |\n| `token-service` | سرور OAuth2/OIDC، صدور و ابطال توکن، افشای JWKS — **بدون کد اختصاصی** | ORY Hydra (Helm chart) |\n| `login-consent-app` | **سرویس مستقل — تنها کد اختصاصی این دامنه** — endpointهای `/login`, `/consent`, `/token-hook` | سرویس سفارشی Go |\n| API Gateway (Traefik) | اعتبارسنجی اولیه توکن، مسیریابی، نرخ‌محدودسازی | Traefik + ForwardAuth / JWKS |\n| میکروسرویس‌ها | اعتبارسنجی محلی JWT با JWKS، اعمال authorization | هر استک |\n| دیتابیس Kratos | ذخیره identity، credentials، sessions | PostgreSQL |\n| دیتابیس Hydra | ذخیره client، consent، grants | PostgreSQL (جدا از Kratos) |\n\n> **نکته طراحی:** Kratos و Hydra باید دیتابیس‌های جدا داشته باشند تا coupling نداشته باشند. این الگوی استاندارد Ory است.\n>\n> `login-consent-app` یک **سرویس مستقل** است (مانند auth-service / iam-service)، نه بخشی از token-service یا auth-service — مستندات آن در [login-consent-app.md](./login-consent-app.md).\n\n---"
    },
    {
      "level": 2,
      "heading": "۵. جریان‌های اصلی",
      "content": "**Core Flows**"
    },
    {
      "level": 3,
      "heading": "۵.۱ تبدیل نشست Kratos به توکن OAuth2 (Login & Consent)",
      "content": "```\n[مرورگر] → GET /oauth2/auth?client_id=...&code_challenge=... → [Hydra]\n[Hydra] → 302 → [login-consent-app]/login?login_challenge=<X>\n[login-consent-app] → GET /admin/oauth2/auth/requests/login?login_challenge=<X> → [Hydra Admin]\n[login-consent-app] → GET /sessions/whoami (با Cookie: ory_kratos_session کاربر) → [Kratos Public]\n  ├─ نشست معتبر:\n  │   [login-consent-app] → PUT /admin/oauth2/auth/requests/login/accept?login_challenge=<X>\n  │                          {subject: \"<kratos-identity-id>\", remember: true}\n  │   [Hydra] → 302 → [login-consent-app]/consent?consent_challenge=<Y>\n  │   [login-consent-app] → PUT /admin/oauth2/auth/requests/consent/accept\n  │   [Hydra] → 302 → redirect_uri?code=<auth_code>\n  └─ نشست نامعتبر:\n      [login-consent-app] → 302 → [Kratos Login]\n```\n\n**نکات کلیدی:**\n- **کوکی `ory_kratos_session` باید forward شود.** بدون آن، whoami همیشه 401 برمی‌گرداند.\n- **Subject = Kratos Identity ID** (identity.id)، نه email/username.\n- **هر login_challenge و consent_challenge یک‌بار مصرف.** خطا → restart flow، retry ممنوع.\n- **skip_consent** برای first-party clients باید **صریحاً** در تنظیمات Hydra client تنظیم شود: `skip_consent: true, skip_logout_consent: true`. از Admin API قابل تنظیم است.\n\n```mermaid\nsequenceDiagram\n    autonumber\n    actor User as User / Browser\n    participant FE as Frontend (SPA/SSR)\n    participant Hydra as Token Service (Hydra)\n    participant LC as login-consent-app\n    participant Kratos as Auth Service (Kratos)\n\n    User->>FE: درخواست ورود\n    FE->>Hydra: GET /oauth2/auth?client_id=...&code_challenge=...\n    Hydra-->>LC: ریدایرکت با login_challenge\n    LC->>Hydra: GET /admin/oauth2/auth/requests/login?login_challenge=<X>\n    Hydra-->>LC: اطلاعات challenge\n    LC->>Kratos: GET /sessions/whoami (با کوکی کاربر)\n    alt نشست Kratos معتبر است\n        Kratos-->>LC: identity.id\n        LC->>Hydra: PUT /admin/oauth2/auth/requests/login/accept (subject=identity.id)\n        Hydra-->>LC: consent_challenge\n        LC->>Hydra: PUT /admin/oauth2/auth/requests/consent/accept\n        Hydra-->>FE: authorization code\n    else نشست معتبر نیست\n        LC-->>User: ریدایرکت به Kratos Login\n    end\n    FE->>Hydra: POST /oauth2/token (grant_type=authorization_code)\n    Hydra-->>FE: access_token (JWT) + refresh_token (+ id_token)\n```"
    },
    {
      "level": 3,
      "heading": "۵.۲ فراخوانی API با توکن",
      "content": "کلاینت access_token را در هدر `Authorization: Bearer <token>` قرار می‌دهد. میکروسرویس‌ها امضای JWT را با کلید عمومی از **JWKS endpoint** هیدرا به‌صورت محلی اعتبارسنجی می‌کنند (کلیدها cache می‌شوند).\n\n```mermaid\nsequenceDiagram\n    autonumber\n    actor Client as Client / Microservice\n    participant GW as API Gateway (اختیاری)\n    participant Svc as Business Service\n    participant JWKS as Token Service (Hydra JWKS)\n\n    Client->>GW: Authorization: Bearer <access_token>\n    GW->>Svc: عبور درخواست\n    Svc->>JWKS: GET /.well-known/jwks.json (cache شده)\n    JWKS-->>Svc: کلید عمومی\n    Svc->>Svc: اعتبارسنجی امضا + استخراج claims (sub, scope, exp)\n    Svc-->>Client: پاسخ کسب‌وکار\n```"
    },
    {
      "level": 3,
      "heading": "۵.۳ تمدید توکن (Refresh)",
      "content": "کلاینت با `refresh_token` به `/oauth2/token` (grant_type=refresh_token) درخواست می‌دهد. Hydra اعتبار refresh_token را (stateful، در دیتابیس خود) بررسی و توکن جدید صادر می‌کند. **Refresh token rotation** فعال است."
    },
    {
      "level": 3,
      "heading": "۵.۴ خروج (Logout) — ترتیب صحیح",
      "content": "1. **ابتدا** توکن‌های Hydra را revoke کنید: `POST /oauth2/revoke` (access + refresh token)\n2. **سپس** نشست Kratos را باطل کنید: `/self-service/logout`\n3. اگر از OIDC Front-Channel/Back-Channel Logout استفاده می‌کنید (برای third-party clients آینده)، از `/oauth2/sessions/logout` استفاده کنید.\n\nصرفاً پاک‌کردن توکن در کلاینت کافی نیست.\n\n---"
    },
    {
      "level": 2,
      "heading": "۶. مدل داده و Claims",
      "content": "**Token Data Model & Claims**"
    },
    {
      "level": 3,
      "heading": "ساختار پیشنهادی JWT access_token",
      "content": "```json\n{\n  \"iss\": \"https://api.nons.ir/v1/auth/hydra\",\n  \"sub\": \"<kratos-identity-id>\",\n  \"aud\": [\"order-service\", \"payment-service\"],\n  \"scope\": \"orders:read orders:write\",\n  \"client_id\": \"web-frontend\",\n  \"exp\": 1735900000,\n  \"iat\": 1735896400\n}\n```\n\nنکات:\n- `sub` = Kratos Identity ID (از whoami). مقدار ثابت و نامتغیر.\n- `aud` محدود به سرویس‌های مجاز.\n- claims اضافی (نقش، سطح دسترسی) از طریق **Hydra OAuth2 Token Hook** اضافه می‌شوند — نه در JWT base.\n- مقدار `iss` محیط‌محور است (رجوع به blueprint §۲.۲)."
    },
    {
      "level": 3,
      "heading": "طول عمر",
      "content": "| توکن | طول عمر | محل ذخیره سمت کلاینت |\n|---|---|---|\n| access_token | ۱۵ دقیقه | حافظه (در BFF: اصلاً به مرورگر نمی‌رسد) |\n| refresh_token | ۳۰ روز (rotation فعال) | httpOnly cookie یا BFF |\n| Kratos session | ۱ تا ۲۴ ساعت (قابل تنظیم) | httpOnly cookie |\n\n> **BFF pattern:** در معماری BFF، access_token و refresh_token هرگز به مرورگر نمی‌رسند — BFF آن‌ها را نگه داشته و یک session cookie به مرورگر می‌دهد.\n\n---"
    },
    {
      "level": 2,
      "heading": "۷. امنیت",
      "content": "**Security**\n\n- **Refresh token rotation** در Hydra فعال شود.\n- **JWKS key rotation** دوره‌ای (هر ۹۰ روز) با overlap.\n- **CORS** محدود به دامنه‌های مجاز (در سطح Gateway).\n- **PKCE** برای کلاینت‌های **عمومی** (SPA، موبایل) **الزامی** است. برای confidential clients (server-side) اختیاری است.\n- **BFF pattern** برای SPA.\n- محدودیت scope per-client در Hydra.\n- لاگ و audit trail برای صدور/ابطال توکن از طریق Hydra webhook.\n- **Admin API فقط از شبکه داخلی** — Kratos Admin و Hydra Admin هرگز public نیستند.\n- Hydra Admin API (port 4445) با **Kubernetes NetworkPolicy** در MVP ایزوله می‌شود (فقط `login-consent-app`)؛ mTLS در Roadmap.\n\n---"
    },
    {
      "level": 2,
      "heading": "۸. توپولوژی استقرار",
      "content": "**Deployment Topology**\n\n```\n[کاربر/مرورگر]\n      |\n[Traefik Gateway]\n      |-----> [auth-service (Kratos)]             --- DB: kratos_db\n      |-----> [token-service (Hydra Public 4444)] --- DB: hydra_db\n      |-----> [login-consent-app] --(NetworkPolicy)--> [Hydra Admin 4445]\n      |          └── endpoints: /login, /consent, /token-hook (تنها کد اختصاصی Go)\n      |-----> [میکروسرویس‌ها ...]                 --- هرکدام JWKS از Hydra cache\n```\n\n- همه سرویس‌های Ory در یک namespace داخلی.\n- میکروسرویس‌ها فقط JWKS عمومی Hydra را می‌خوانند.\n- Admin APIهای Kratos و Hydra فقط شبکه داخلی — `NetworkPolicy` دسترسی به Hydra Admin (4445) را فقط به `login-consent-app` محدود می‌کند.\n\n---"
    },
    {
      "level": 2,
      "heading": "۹. رویدادها",
      "content": "**Events**\n\nسرویس `token-service` رویدادهای زیر را مطابق با استاندارد `ADR-EVENT-001` منتشر می‌کند:\n\n| رویداد | زمان صدور | توضیح |\n|---|---|---|\n| `nons.token.issued` | پس از صدور موفق access/refresh token | شامل `client_id`, `sub`, `scope`, `grantedAt` |\n| `nons.token.revoked` | پس از ابطال توکن / logout | شامل `sub`, `client_id`, `revokedAt` |\n\nتعاریف رسمی و Payload دقیق در `nons-api/catalog/events/token/events.yaml` ثبت شده‌اند.\n\n---"
    },
    {
      "level": 2,
      "heading": "۱۰. فازهای پیاده‌سازی",
      "content": "**Implementation Phases**\n\n| فاز | محدوده | خروجی |\n|---|---|---|\n| فاز ۱ | راه‌اندازی Kratos + auth-service (session-based) | auth-service قابل استفاده مستقل |\n| فاز ۲ | راه‌اندازی Hydra (Helm) + **login-consent-app** (شامل /login, /consent, /token-hook) | صدور access/refresh token واقعی |\n| فاز ۳ | پیاده‌سازی middleware اعتبارسنجی JWT در یک سرویس pilot | اثبات الگوی stateless validation |\n| فاز ۴ | افزودن Gateway (Oathkeeper/Fortress) برای اعتبارسنجی متمرکز | کاهش تکرار کد در سرویس‌ها |\n| فاز ۵ | claims enrichment، rotation کلید، سخت‌سازی امنیتی | آماده برای production |\n| فاز ۶ | Rollout به همه میکروسرویس‌ها، مانیتورینگ | معماری کامل در تولید |\n\n---"
    },
    {
      "level": 2,
      "heading": "۱۱. سوالات باز باقی‌مانده",
      "content": ""
    },
    {
      "level": 3,
      "heading": "موارد غیربحرانی (خارج از MVP، مانع توسعه نیستند)",
      "content": "1. **Third-party OAuth2 در MVP:** فعلاً فقط first-party. در صورت نیاز، DCR فعال شود.\n2. **ابطال آنی JWT:** پیش‌فرض بدون Redis blacklist (مطابق ADR-Gateway-001) — انقضای طبیعی access_token (۱۵ دقیقه) کافی است.\n3. **CORS و Rate Limiting:** در سطح Gateway.\n4. **Audit trail:** ذخیره از طریق NATS event → audit-service.\n\n---\n\n> **وضعیت سند:** FINAL — تمام تصمیمات معماری در [`ADR-Backend-006`](../../backend/ADR/ADR-Backend-006) ثبت شده‌اند. توسعه فاز ۱–۲ می‌تواند آغاز شود.\n>\n> منابع: `nons-api/services/token-service/blueprint.md` (نسخه ۳.۳), [`ADR-Backend-006`](../../backend/ADR/ADR-Backend-006)"
    }
  ]
}