همه نوشته‌ها

راهنمای authetication

راهنمای authetication

راهنمای authetication

راهنمای Authentication

Authentication یکی از پایه‌ای‌ترین مفاهیم امنیت در نرم‌افزار است، اما اصطلاحات اطراف آن آن‌قدر زیادند که خیلی راحت با هم قاطی می‌شوند: Session، Cookie، Basic Auth، API Key، Bearer Token، JWT، OAuth، OpenID Connect، Access Token، Refresh Token و MFA.

مشکل اصلی این است که همه‌ی این‌ها یک چیز نیستند و حتی در یک لایه هم کار نمی‌کنند. مثلاً JWT یک فرمت Token است، Bearer روشی برای ارائه‌ی Token است و OAuth یک چارچوب Authorization است.

برای فهم درست این مفاهیم، بهتر است از پایه شروع کنیم.


Authentication و Authorization چه فرقی دارند؟

قبل از هر چیز باید دو مفهوم را از هم جدا کنیم.

Authentication یعنی:

تو چه کسی هستی؟

و Authorization یعنی:

حالا که می‌دانم چه کسی هستی، اجازه داری چه کارهایی انجام بدهی؟

مثلاً زمانی که وارد یک سایت می‌شویم و Username و Password خود را وارد می‌کنیم، سیستم در حال Authentication است.

اما زمانی که سیستم بررسی می‌کند آیا این کاربر اجازه دارد وارد پنل مدیریت شود، یک سفارش را حذف کند یا اطلاعات کاربر دیگری را ببیند، بحث Authorization مطرح می‌شود.

به شکل ساده:

Authentication
      ↓
Who are you?

Authorization
      ↓
What are you allowed to do?

در بیشتر سیستم‌ها Authentication ابتدا انجام می‌شود و سپس بر اساس هویت کاربر، Authorization انجام می‌شود.


Username و Password

آشناترین روش Authentication استفاده از Username و Password است.

کاربر اطلاعات خود را وارد می‌کند:

Username: ali
Password: MySecret123

سرور بررسی می‌کند که آیا این اطلاعات معتبر هستند یا خیر.

اگر معتبر باشند، Authentication موفق شده و سیستم متوجه می‌شود این کاربر چه کسی است.

اما نکته‌ی بسیار مهم این است که Password نباید به صورت خام در Database ذخیره شود.

یعنی این کار اشتباه است:

ali | hello123

چون اگر Database لو برود، Password تمام کاربران نیز لو می‌رود.

در عوض Password باید با یک الگوریتم مناسب مخصوص Password Hashing مثل Argon2 یا bcrypt پردازش شود.

به صورت مفهومی:

hello123
    ↓
Password Hashing
    ↓
$argon2id$v=19$...

سپس Hash در Database ذخیره می‌شود.

هنگام Login، سرور Password واردشده توسط کاربر را با Hash ذخیره‌شده مقایسه می‌کند.


Hashing با Encryption فرق دارد

این دو مفهوم را نباید با هم اشتباه بگیریم.

Encryption به صورت کلی قابل بازگردانی است:

Data
 ↓
Encryption
 ↓
Encrypted Data
 ↓
Decryption
 ↓
Data

اما Password Hashing به شکلی طراحی می‌شود که یک‌طرفه باشد:

Password
   ↓
Hash

قرار نیست Server بتواند Hash را Decode کند و دوباره Password اصلی را به دست بیاورد.

برای Passwordها معمولاً از Password Hashing همراه Salt و الگوریتم‌هایی استفاده می‌شود که عمداً نسبتاً پرهزینه طراحی شده‌اند تا حملات حدس Password سخت‌تر شوند.


مشکل Passwordها

بخش بزرگی از مشکل Passwordها به رفتار کاربران مربوط می‌شود.

کاربران ممکن است Passwordهایی مثل این انتخاب کنند:

123456
password
qwerty

یا یک Password را روی چند سایت مختلف استفاده کنند.

اگر اطلاعات Login یکی از این سایت‌ها لو برود، مهاجم می‌تواند همان Username و Password را روی سرویس‌های دیگر نیز امتحان کند.

به این حمله Credential Stuffing گفته می‌شود.

به همین دلیل Password به تنهایی همیشه امنیت کافی ایجاد نمی‌کند و در سیستم‌های حساس معمولاً در کنار آن از MFA نیز استفاده می‌شود.


Session و Cookie

فرض کنیم User یک بار Username و Password خود را وارد کرده و Login شده است.

یک روش بسیار بد این است که Client در هر درخواست دوباره Username و Password را ارسال کند:

GET /profile
username=ali
password=123456

بعد:

GET /orders
username=ali
password=123456

و دوباره:

GET /messages
username=ali
password=123456

برای جلوگیری از این اتفاق، یکی از روش‌های سنتی و بسیار رایج استفاده از Session است.

پس از Login موفق، Server یک Session ایجاد می‌کند.

مثلاً:

Session ID:

abc98df712

سپس روی Server چیزی شبیه این وجود خواهد داشت:

abc98df712 → user_id = 42

به این ترتیب Server می‌داند Session با شناسه‌ی abc98df712 متعلق به چه کاربری است.


Cookie مکانیزمی در Browser برای نگهداری اطلاعات کوچک و ارسال آن‌ها در Requestهای مناسب است.

Server می‌تواند در Response بگوید:

Set-Cookie: sessionId=abc98df712

Browser این Cookie را ذخیره می‌کند.

در Requestهای بعدی Browser می‌تواند آن را به صورت خودکار ارسال کند:

Cookie: sessionId=abc98df712

Server Session ID را دریافت می‌کند و در Session Store جست‌وجو می‌کند:

abc98df712
     ↓
user_id = 42
     ↓
Ali

پس Server متوجه می‌شود Request متعلق به Ali است.

جریان کلی چیزی شبیه این است:

User
 │
 │ Username + Password
 ▼
Server
 │
 │ Credentials are valid
 ▼
Create Session
 │
 │ Session ID = abc123
 ▼
Browser
 │
 │ Stores Session ID in Cookie
 ▼
Future Requests

این نکته اهمیت زیادی دارد.

Session معمولاً وضعیت Authentication را در سمت Server نگهداری می‌کند.

مثلاً:

abc123:
{
    userId: 42,
    role: "admin"
}

در مقابل Cookie روی Browser وجود دارد و ممکن است فقط Session ID را نگه دارد:

sessionId=abc123

پس به صورت ساده:

Session → Server-side state

Cookie → Browser mechanism

Cookie می‌تواند برای چیزهای مختلف استفاده شود و صرفاً مخصوص Authentication نیست.


Logout در Session-Based Authentication

اگر Server Session را حذف کند:

delete session abc123

حتی اگر Browser دوباره این Cookie را بفرستد:

Cookie: sessionId=abc123

Server دیگر Session معتبری برای آن پیدا نمی‌کند.

بنابراین User دیگر Authenticated محسوب نمی‌شود.

یکی از مزایای Session همین کنترل مستقیم Server است.

Server می‌تواند یک Session را فوراً Invalid کند.


Session در سیستم‌های بزرگ

اگر میلیون‌ها User داشته باشیم، مدیریت Sessionها می‌تواند پیچیده‌تر شود.

به همین دلیل در معماری‌های بزرگ ممکن است Sessionها در سیستم‌هایی مانند Redis نگهداری شوند تا چند Server بتوانند به Session Store مشترک دسترسی داشته باشند.


Basic Authentication

Basic Authentication یکی از ساده‌ترین روش‌های Authentication در HTTP است.

Client ابتدا Username و Password را با یکدیگر ترکیب می‌کند:

ali:123456

سپس آن را به Base64 تبدیل می‌کند:

YWxpOjEyMzQ1Ng==

و در HTTP Header قرار می‌دهد:

Authorization: Basic YWxpOjEyMzQ1Ng==

Base64 رمزنگاری نیست

این نکته بسیار مهم است.

Base64 نوعی Encoding است، نه Encryption.

اگر کسی این مقدار را داشته باشد:

YWxpOjEyMzQ1Ng==

خیلی راحت می‌تواند آن را Decode کند و دوباره به این مقدار برسد:

ali:123456

بنابراین:

Base64 ≠ Encryption

Basic Authentication باید حتماً روی HTTPS استفاده شود.

HTTPS است که از اطلاعات در مسیر انتقال محافظت می‌کند، نه Base64.


تفاوت Basic Auth با Session

در Session-Based Authentication معمولاً User یک بار Login می‌کند:

Login
 ↓
Session ID
 ↓
Future Requests

اما در Basic Authentication، Credential معمولاً در هر Request ارسال می‌شود:

Request 1 → Username + Password
Request 2 → Username + Password
Request 3 → Username + Password

به همین دلیل Basic Auth بیشتر برای APIهای ساده، ابزارهای داخلی، محیط‌های Development یا سناریوهای محدود استفاده می‌شود و معمولاً گزینه‌ی اول برای یک Web Application عمومی و مدرن نیست.


API Key

گاهی User یک انسان نیست.

ممکن است یک Application یا Service بخواهد API ما را صدا بزند.

فرض کنیم یک Weather API داریم.

به هر Application یک API Key داده می‌شود:

sk_abc123xyz

سپس Client آن را در Request ارسال می‌کند:

GET /weather

x-api-key: sk_abc123xyz

Server API Key را بررسی می‌کند و متوجه می‌شود Request مربوط به چه Client یا Projectی است.

مثلاً:

sk_abc123xyz
      ↓
Company ABC

API Key چه کاربردهایی دارد؟

API Key می‌تواند برای موارد مختلف استفاده شود:

API Access
Rate Limiting
Billing
Usage Tracking

مثلاً Server می‌تواند بداند:

API Key X
→ 10,000 Requests / Month

API Key معمولاً هویت Application را مشخص می‌کند

یکی از نکات مهم این است که API Key لزوماً مشخص نمی‌کند User انسانی چه کسی است.

ممکن است فقط مشخص کند:

API Key
   ↓
MyWeatherApp

اما Server هنوز نداند User فعلی Ali است یا Reza.

به همین دلیل API Key و User Authentication الزاماً یک مفهوم نیستند.


API Key یک Secret است

اگر API Key لو برود، مهاجم ممکن است بتواند از API با هویت همان Client استفاده کند.

بنابراین API Keyهای Secret نباید در Repository عمومی، Frontend JavaScript یا جاهایی که کاربران می‌توانند آن‌ها را ببینند قرار بگیرند.

API Key نیز باید مانند یک Credential حساس محافظت شود.


Token-Based Authentication

در مدل Token-Based، کاربر ابتدا Authentication انجام می‌دهد.

مثلاً:

Username + Password

Server Credential را بررسی می‌کند و در صورت معتبر بودن یک Token به Client می‌دهد:

token = abc123xyz

از این لحظه به بعد Client لازم نیست Password را در Requestهای مختلف ارسال کند.

در عوض Token را ارسال می‌کند.

مثلاً:

Authorization: Bearer abc123xyz

Token چیست؟

Token در اصل یک Credential است که Client با استفاده از آن می‌تواند دسترسی خود را اثبات کند.

Token ممکن است فقط یک رشته‌ی تصادفی باشد:

2f7c9d885937a...

که معمولاً به چنین Tokenهایی Opaque Token گفته می‌شود.

یا ممکن است Structured باشد، مثل JWT:

eyJhbGciOi...

پس Token الزاماً JWT نیست.

این جمله را باید به خاطر سپرد:

هر JWT می‌تواند به عنوان Token استفاده شود، اما هر Token الزاماً JWT نیست.


Bearer Token چیست؟

Bearer را نباید با JWT اشتباه گرفت.

Bearer یک Token Format خاص نیست.

Bearer بیشتر درباره‌ی نحوه‌ی استفاده از Credential است.

مثلاً:

Authorization: Bearer <token>

مفهوم Bearer تقریباً این است:

کسی که این Token را در اختیار دارد، می‌تواند از اعتبار آن استفاده کند.

یک مثال ساده، بلیط سینما است.

اگر بلیط معتبر داشته باشیم:

Ticket
  ↓
Entry

معمولاً داشتن بلیط برای ورود کافی است.

Bearer Token نیز تا حدی همین ویژگی را دارد.

به همین دلیل اگر Bearer Token لو برود، مهاجم ممکن است بتواند تا زمانی که Token معتبر است با همان Credential درخواست ارسال کند.

پس Bearer Token باید به شدت محافظت شود.


Access Token

Tokenی که Client معمولاً برای دسترسی به یک API یا Resource Server استفاده می‌کند، Access Token نام دارد.

مثلاً:

GET /profile

Authorization: Bearer ACCESS_TOKEN

Access Token معمولاً مدت اعتبار نسبتاً کوتاهی دارد.

مثلاً:

15 Minutes

اگر Access Token دزدیده شود، مهاجم فقط تا زمان Expiration آن امکان استفاده از همان Token را خواهد داشت.

مثلاً:

12:00 → Token stolen
12:15 → Token expires

کوتاه بودن عمر Access Token نوعی محدود کردن خسارت یا Damage Limitation است.


Refresh Token

اگر Access Token بعد از مدت کوتاهی Expire شود، نمی‌خواهیم User مجبور باشد هر چند دقیقه یک بار Username و Password خود را وارد کند.

اینجاست که Refresh Token وارد می‌شود.

Client می‌تواند Refresh Token را به Authorization Server بدهد:

Refresh Token
      ↓
Authorization Server
      ↓
New Access Token

بنابراین User می‌تواند Session طولانی‌تری داشته باشد، در حالی که Access Tokenهایی که مرتب برای API استفاده می‌شوند کوتاه‌عمر هستند.


مگر Refresh Token نمی‌تواند لو برود؟

چرا.

Refresh Token هم می‌تواند دزدیده شود و در واقع اگر Refresh Token معتبر لو برود، ممکن است خطر آن از Access Token بیشتر باشد.

مهاجمی که Access Token دزدیده معمولاً فقط تا زمان Expiration آن دسترسی دارد.

اما مهاجمی که Refresh Token معتبر در اختیار داشته باشد ممکن است بتواند Access Token جدید دریافت کند.

مثلاً:

Stolen Refresh Token
        ↓
Authorization Server
        ↓
New Access Token

بنابراین هدف Refresh Token این نیست که Credential غیرقابل سرقتی ایجاد کنیم.

هدف این است که Credential حساس‌تر را کمتر در معرض استفاده قرار دهیم و بیشتر از آن محافظت کنیم.


چرا Access Token کوتاه است ولی Refresh Token بلندتر؟

Access Token ممکن است در تعداد زیادی Request استفاده شود:

GET /profile
Authorization: Bearer ACCESS_TOKEN
GET /orders
Authorization: Bearer ACCESS_TOKEN
POST /comments
Authorization: Bearer ACCESS_TOKEN

بنابراین Access Token در یک مسیر پرتردد استفاده می‌شود.

در مقابل Refresh Token ideally فقط وقتی لازم است Access Token جدید ساخته شود استفاده می‌شود.

مثلاً:

Client
   │
   │ Refresh Token
   ▼
Authorization Server
   │
   │ New Access Token
   ▼
Client

بنابراین در طراحی درست:

Access Token
→ Frequent usage
→ Greater exposure
→ Short lifetime

Refresh Token
→ Limited usage
→ Stronger protection
→ Longer lifetime

اما اگر Access Token و Refresh Token هر دو داخل Cookie باشند چه؟

این سؤال بسیار مهمی است.

فرض کنیم هر دو Token را این‌طور ذخیره کنیم:

access_token=AAA;
HttpOnly;
Secure;
Path=/

refresh_token=RRR;
HttpOnly;
Secure;
Path=/

در این حالت Browser ممکن است در Requestهای مختلف هر دو Cookie را ارسال کند.

مثلاً:

GET /api/profile

و Browser ارسال کند:

Cookie:
access_token=AAA;
refresh_token=RRR;

بعد:

GET /api/products

باز هر دو Token ارسال شوند.

در چنین طراحی‌ای یکی از مزایای اصلی جدا کردن Access Token و Refresh Token ضعیف می‌شود، چون Refresh Token بدون نیاز در تمام Requestها در معرض Backend قرار گرفته است.


طراحی بهتر Cookieها

می‌توان Scope مربوط به Cookieها را محدود کرد.

مثلاً:

Set-Cookie: access_token=AAA;
            HttpOnly;
            Secure;
            Path=/api

و:

Set-Cookie: refresh_token=RRR;
            HttpOnly;
            Secure;
            Path=/auth/refresh

در این حالت وقتی Browser Request زیر را می‌فرستد:

GET /api/profile

Access Token برای مسیر API قابل ارسال است.

اما Refresh Token برای Endpoint عادی API در Scope نیست.

زمانی که Access Token Expire می‌شود، Client می‌تواند این Endpoint را صدا بزند:

POST /auth/refresh

و در آنجا Refresh Token استفاده شود.

یعنی از نظر معماری:

Access Token
     ↓
Resource Server / API

Refresh Token
     ↓
Authorization Server / Refresh Endpoint

این تفاوت بسیار مهم‌تر از صرفاً متفاوت بودن Expiration Time آن‌ها است.


HttpOnly دقیقاً چه کاری انجام می‌دهد؟

خیلی وقت‌ها گفته می‌شود Token را در HttpOnly Cookie قرار دهید.

اما باید بدانیم HttpOnly دقیقاً چه کاری انجام می‌دهد.

HttpOnly به این معنی نیست که Cookie به Server ارسال نمی‌شود.

برعکس، Browser همچنان Cookie را در Requestهای مناسب ارسال می‌کند.

HttpOnly فقط دسترسی JavaScript معمولی صفحه به مقدار Cookie را محدود می‌کند.

مثلاً JavaScript نتواند به سادگی با:

document.cookie

مقدار آن Cookie را بخواند.

پس:

HttpOnly
    ↓
Prevents normal JavaScript access to Cookie value

Secure چه کاری انجام می‌دهد؟

وقتی Cookie دارای:

Secure

باشد، Browser آن را فقط روی ارتباط HTTPS ارسال می‌کند.

یعنی:

Secure
   ↓
Cookie only over HTTPS

SameSite چه کاری انجام می‌دهد؟

SameSite رفتار ارسال Cookie در Requestهای Cross-Site را کنترل می‌کند و یکی از ابزارهایی است که می‌تواند در کاهش بعضی حملات CSRF نقش داشته باشد.

به صورت مفهومی:

SameSite
    ↓
Controls cross-site cookie behavior

Path و Domain چه کاری انجام می‌دهند؟

Path مشخص می‌کند Cookie برای چه مسیرهایی قابل ارسال باشد.

Domain مشخص می‌کند Cookie برای چه Host/Domainهایی معتبر باشد.

پس می‌توان گفت:

HttpOnly
→ JavaScript access

Secure
→ HTTPS only

SameSite
→ Cross-site behavior

Path / Domain
→ Where the Cookie is sent

هر کدام یک مشکل متفاوت را حل می‌کنند.


Access Token و Refresh Token قدرت یکسانی ندارند

حتی اگر هر دو Token محسوب شوند، وظیفه‌ی آن‌ها یکسان نیست.

Access Token برای دسترسی به Resource Server است:

Access Token
     ↓
API
     ↓
GET /orders
POST /comments
GET /profile

اما Refresh Token نباید Credential مستقیم API باشد.

یعنی این Request اصولاً نباید معتبر باشد:

GET /orders

Authorization: Bearer REFRESH_TOKEN

Resource Server باید چنین Tokenی را رد کند.

Refresh Token فقط برای گرفتن Access Token جدید است:

Refresh Token
      ↓
Authorization Server
      ↓
Access Token

پس تفاوت فقط این نیست که یکی ۱۵ دقیقه و دیگری چند روز اعتبار دارد.

Audience و محل استفاده‌ی آن‌ها هم متفاوت است.


Refresh Token Rotation

یکی از روش‌های مهم افزایش امنیت Refresh Token، Refresh Token Rotation است.

فرض کنیم Client در ابتدا این Refresh Token را دارد:

RT1

زمانی که از آن استفاده می‌کند:

RT1
 ↓
Authorization Server
 ↓
Access Token + RT2

Server، RT1 را Invalid می‌کند.

حالا:

RT1 → Invalid
RT2 → Valid

بار بعد:

RT2
 ↓
Authorization Server
 ↓
Access Token + RT3

و سپس:

RT2 → Invalid
RT3 → Valid

به این ترتیب هر Refresh Token فقط یک بار قابل استفاده است.

اگر بعداً Token قدیمی مثل RT1 دوباره دیده شود:

Attacker
   ↓
RT1
   ↓
Server
   ↓
This token was already used

این اتفاق می‌تواند علامتی از سرقت Token باشد.

Server ممکن است در چنین شرایطی Session یا مجموعه‌ی مرتبط Tokenها را Revoke کند.

این مفهوم با عنوان‌هایی مثل Refresh Token Rotation و Reuse Detection شناخته می‌شود.


اگر مهاجم هم Access Token و هم Refresh Token را بدزدد چه؟

در این شرایط خطر جدی است.

Access Token + Refresh Token قرار نیست Security را به شکل جادویی حل کنند.

Security معمولاً درباره‌ی کم کردن احتمال حمله و محدود کردن دامنه‌ی خسارت است.

اگر مهاجم بتواند کل Credentialهای Client را به دست بیاورد، ممکن است بتواند هم از Access Token استفاده کند و هم با Refresh Token، Access Tokenهای جدید دریافت کند.

به همین دلیل برای Refresh Token معمولاً اقدامات محافظتی قوی‌تری در نظر گرفته می‌شود:

Secure Storage
Short/limited lifetime
Rotation
Revocation
Reuse Detection
Appropriate Cookie scope

BFF چیست و چرا گاهی Token اصلاً در Browser نیست؟

در بعضی Web Applicationها معماری دیگری استفاده می‌شود.

به جای اینکه Access Token و Refresh Token مستقیماً در Browser نگهداری شوند، Backend آن‌ها را نگه می‌دارد.

Browser فقط یک Session Cookie دارد.

مثلاً:

Browser
   │
   │ Session Cookie
   ▼
Backend / BFF
   │
   ├── Access Token
   └── Refresh Token
   │
   ▼
APIs / Authorization Server

در این مدل:

Browser
❌ Access Token
❌ Refresh Token

Backend
✅ Access Token
✅ Refresh Token

به چنین الگوهایی معمولاً Backend for Frontend یا BFF گفته می‌شود.

بنابراین هیچ قانونی وجود ندارد که بگوید Access Token و Refresh Token حتماً باید مستقیماً در Browser و داخل Cookie ذخیره شوند.


JWT چیست؟

JWT مخفف:

JSON Web Token

است.

JWT یک Authentication Protocol نیست.

بلکه یک Token Format استاندارد است.

این تفاوت بسیار مهم است.

JWT ≠ Authentication Protocol

JWT = Token Format

ساختار JWT

JWT معمولاً سه بخش دارد:

xxxxx.yyyyy.zzzzz

یعنی:

Header.Payload.Signature

مثلاً:

eyJhbGciOiJIUzI1NiJ9
.
eyJzdWIiOiIxMjMifQ
.
SflKxwRJSMeKK...

Header معمولاً Metadata مربوط به Token را نگه می‌دارد.

مثلاً:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

Payload شامل Claimها است.

مثلاً:

{
  "sub": "12345",
  "name": "Ali",
  "role": "admin",
  "exp": 1770000000
}

به اطلاعات داخل JWT معمولاً Claim گفته می‌شود.

مثلاً:

sub

معمولاً Subject را مشخص می‌کند.

و:

exp

Expiration Time را مشخص می‌کند.


Signature

بخش Signature کمک می‌کند Receiver تشخیص دهد Token به صورت غیرمجاز دستکاری نشده است.

به شکل مفهومی:

Header
+
Payload
+
Secret / Private Key
       ↓
Signature

فرض کنیم مهاجم داخل Payload مقدار:

"role": "user"

را به:

"role": "admin"

تغییر دهد.

اگر Signature Validation به درستی انجام شود، Token دیگر معتبر نخواهد بود.


آیا اطلاعات JWT مخفی هستند؟

لزوماً نه.

این یکی از رایج‌ترین سوءبرداشت‌ها درباره‌ی JWT است.

Payload یک JWT معمولی معمولاً قابل Decode شدن است.

بنابراین نباید اطلاعات Secret را فقط به این دلیل که داخل JWT قرار دارند، امن فرض کنیم.

مثلاً قرار دادن چنین اطلاعاتی ایده‌ی خوبی نیست:

{
  "password": "secret",
  "creditCard": "..."
}

Signature بیشتر مسئله‌ی Integrity را حل می‌کند.

یعنی:

Has the data been modified?

نه الزاماً Confidentiality را:

Can anyone see the data?

به زبان ساده:

Signed بودن JWT به معنی Encrypted بودن آن نیست.


مزیت JWT چیست؟

JWT می‌تواند اطلاعاتی را داخل خودش حمل کند.

مثلاً:

{
  "userId": 42,
  "role": "admin"
}

Resource Server می‌تواند بعد از Validation مناسب Token، بعضی از اطلاعات لازم را مستقیماً از Claimهای آن دریافت کند.

به همین دلیل JWT در معماری‌های Distributed و API-Based بسیار رایج شده است.


مشکل JWT و Logout

فرض کنیم JWT تا ساعت 18:00 معتبر است.

User ساعت 17:00 Logout می‌کند.

اگر سیستم کاملاً Stateless باشد، JWT از نظر Cryptographic ممکن است همچنان تا ساعت 18:00 معتبر باشد.

یعنی:

17:00 → User logs out

JWT
 ↓
Still valid until 18:00

به همین دلیل Revocation در سیستم‌های JWT می‌تواند پیچیده‌تر از Sessionهای کاملاً Server-Side باشد.

راه‌حل‌های مختلفی وجود دارد، مثل:

Short-lived Access Tokens
Revocation mechanisms
Token deny lists in some architectures
Refresh Token revocation
Session tracking

پس Stateless بودن همیشه به معنی «بهتر بودن» نیست؛ Trade-off دارد.


OAuth چیست؟

OAuth یکی از اصطلاحاتی است که بیشترین سوءتفاهم درباره‌ی آن وجود دارد.

OAuth اساساً برای Delegated Authorization طراحی شده است.

یعنی:

چگونه به یک Application اجازه بدهیم بدون دادن Password خودمان به آن، به بخش محدودی از Resourceهای ما دسترسی داشته باشد؟


مشکل Password Sharing

فرض کنیم یک Application برای چاپ عکس ساخته شده و می‌خواهد عکس‌های حساب ما را بخواند.

روش بسیار بد این است که Application بگوید:

Google Username?
Google Password?

و User اطلاعات Login اصلی حساب Google خود را به آن Application بدهد.

این کار خطرناک است، چون Application عملاً Password کامل User را در اختیار گرفته است.

OAuth سعی می‌کند این مسئله را حل کند.

به جای Password Sharing، Application یک Access Token محدود دریافت می‌کند.

جریان کلی:

Photo App
   ↓
Authorization Server
   ↓
User gives permission
   ↓
Photo App receives limited access

بدون اینکه Application Password اصلی User را دریافت کند.


نقش‌های اصلی در OAuth

برای فهم OAuth چهار نقش مهم داریم.

Resource Owner

معمولاً همان User است.

مثلاً:

Ali

User مالک Resource یا کنترل‌کننده‌ی دسترسی به آن است.


Client

Applicationی است که می‌خواهد به Resource دسترسی پیدا کند.

مثلاً:

Photo Printing App

Authorization Server

سیستمی است که Authorization را مدیریت کرده و Token صادر می‌کند.

User در تعامل با این Server Authentication و Consent مربوط به دسترسی را انجام می‌دهد.


Resource Server

سرویسی است که Resource واقعی را نگه می‌دارد.

مثلاً:

Photos API

و Access Token را برای دسترسی به Resource بررسی می‌کند.


جریان ساده OAuth

Client می‌خواهد به Photos User دسترسی پیدا کند.

ابتدا User به Authorization Server هدایت می‌شود.

مثلاً صفحه‌ای می‌بیند:

PhotoApp wants permission to:

✓ Read your photos

User می‌تواند اجازه بدهد:

Allow

در یک جریان رایج، Client ابتدا یک Authorization Code دریافت می‌کند.

سپس:

Authorization Code
       ↓
Authorization Server
       ↓
Access Token

Client اکنون Access Token دارد و می‌تواند Resource Server را صدا بزند:

GET /photos

Authorization: Bearer ACCESS_TOKEN

Resource Server Token را Validate کرده و در صورت مجاز بودن Request، Resource را برمی‌گرداند.


Scope چیست؟

OAuth این امکان را می‌دهد که Access را محدود کنیم.

مثلاً Client فقط اجازه‌ی خواندن عکس‌ها را داشته باشد:

photos.read

نه حذف کردن آن‌ها.

یا Scopeهای دیگری داشته باشیم:

contacts.read
calendar.write
profile.read

Scope مشخص می‌کند Client چه نوع Accessی درخواست کرده یا دریافت کرده است.

این موضوع با یکی از اصول اصلی Security هماهنگ است:

Principle of Least Privilege

یعنی:

فقط کمترین دسترسی لازم را بده.


OAuth برای Login است؟

نکته‌ی مهم این است که OAuth اساساً درباره‌ی Authorization است.

سؤال اصلی OAuth چیزی شبیه این است:

What can this application access?

نه الزاماً:

Who exactly is this user?

برای Authentication و Identity معمولاً لایه‌ی دیگری به نام OpenID Connect استفاده می‌شود.


OpenID Connect یا OIDC

OpenID Connect که به اختصار OIDC نامیده می‌شود، یک Identity Layer روی OAuth است.

به صورت بسیار ساده:

OAuth
+
Identity Layer
=
OpenID Connect

OIDC برای سناریوهایی مثل Login و Single Sign-On بسیار رایج است.


Access Token و ID Token

در OIDC معمولاً باید Access Token و ID Token را از یکدیگر جدا کنیم.

Access Token

Access Token برای API یا Resource Server است:

Client
   ↓
Access Token
   ↓
API

مثلاً:

Authorization: Bearer ACCESS_TOKEN

ID Token

ID Token برای Client است و اطلاعات مربوط به Authentication و Identity را بیان می‌کند.

ID Token معمولاً یک JWT است.

مثلاً:

{
  "sub": "983725",
  "iss": "https://identity.example",
  "aud": "my-app",
  "exp": 1770000000
}

Client بعد از انجام Validation مناسب می‌تواند بفهمد Authentication مربوط به چه Subjectی بوده است.


تفاوت Access Token و ID Token

به صورت خلاصه:

Access Token
→ API Access

ID Token
→ Authentication / Identity information

نباید ID Token و Access Token را جای یکدیگر استفاده کرد.


Login with Google چگونه به این مفاهیم مربوط است؟

وقتی User روی چیزی مثل:

Continue with Google

کلیک می‌کند، Application او را به Identity Provider هدایت می‌کند.

User در آنجا Authentication انجام می‌دهد.

Application از طریق یک Flow استاندارد اطلاعات لازم را دریافت می‌کند و می‌تواند برای User Session ایجاد کند.

در چنین سناریوهایی معمولاً OpenID Connect نقش مهمی در Identity دارد.

بنابراین اگر بخواهیم خیلی ساده تفاوت را بیان کنیم:

OAuth
→ Delegated Authorization

OIDC
→ Authentication + Identity

MFA چیست؟

MFA مخفف:

Multi-Factor Authentication

است.

هدف MFA این است که برای اثبات هویت فقط به یک Credential مانند Password تکیه نکنیم.

مثلاً:

Password
+
Second Factor

Authentication Factorها

Authentication Factorها معمولاً در چند دسته قرار می‌گیرند.

Something You Know

چیزی که می‌دانید:

Password
PIN

Something You Have

چیزی که در اختیار دارید:

Phone
Authenticator Device
Hardware Security Key

Something You Are

ویژگی فیزیکی یا Biometric:

Fingerprint
Face

آیا Password + PIN یعنی MFA؟

لزوماً نه.

چون هر دو در یک دسته هستند:

Password
+
PIN

هر دو:

Something You Know

محسوب می‌شوند.

MFA واقعی معمولاً از Factorهای مستقل استفاده می‌کند.

مثلاً:

Password
+
Security Key

در اینجا یکی Something You Know و دیگری Something You Have است.


OTP

یکی از مدل‌های رایج Factor دوم، One-Time Password است.

User ابتدا Password را وارد می‌کند.

سپس سیستم درخواست می‌کند:

Enter your 6-digit code

مثلاً Authenticator App این کد را نمایش می‌دهد:

418239

حالا اگر مهاجم فقط Password را دزدیده باشد، هنوز برای ورود به Factor دوم نیز نیاز دارد.

SMS نیز می‌تواند برای Factor دوم استفاده شود، هرچند روش‌های قوی‌تری مانند Authenticator Applicationها و Hardware Security Keyها نیز وجود دارند.


RFC چیست؟

در بحث Authentication و Internet Protocolها زیاد با عباراتی مثل این برخورد می‌کنیم:

RFC 7519
RFC 6750
RFC 6749

RFC مخفف:

Request for Comments

است.

اسم آن ممکن است کمی عجیب به نظر برسد، اما RFCها اسناد فنی بسیار مهم اینترنت هستند.

بسیاری از Protocolها، Formatها و استانداردهای اینترنت در RFCها دقیقاً مستند می‌شوند.

مثلاً:

RFC 7519 → JWT

RFC 6750 → Bearer Token Usage

RFC 6749 → OAuth 2.0

البته هر RFC الزاماً یک Internet Standard نهایی نیست؛ RFCها دسته‌های مختلفی دارند.

اما در سطح کاربردی می‌توان آن‌ها را اسناد رسمی فنی دانست که رفتار Protocolها و استانداردها را دقیق توضیح می‌دهند.


IETF چیست؟

بسیاری از RFCهای مهم از فرایندهای مرتبط با IETF منتشر می‌شوند.

IETF مخفف:

Internet Engineering Task Force

است.

به صورت ساده:

Experts / Working Groups
       ↓
Technical Specification
       ↓
RFC
       ↓
Implementations

وجود این استانداردها باعث می‌شود شرکت‌ها و زبان‌های برنامه‌نویسی مختلف بتوانند با یکدیگر کار کنند.

مثلاً اگر یک Backend نوشته‌شده با Java و یک Service نوشته‌شده با Go هر دو JWT استاندارد تولید و Validate کنند، هر دو درباره‌ی ساختار Token درک مشترکی دارند.


ارتباط تمام این مفاهیم با یکدیگر

حالا می‌توانیم تمام قطعات را کنار هم قرار دهیم.

فرض کنیم یک فروشگاه اینترنتی داریم.

User ابتدا:

Username + Password

وارد می‌کند.

این Credential برای Authentication استفاده می‌شود.

سیستم ممکن است برای امنیت بیشتر درخواست کند:

Password
+
Authenticator Code

این MFA است.

پس از Authentication، سیستم ممکن است یک Session ایجاد کند.

Session
   ↓
Session ID
   ↓
Cookie

یا ممکن است معماری Token-Based داشته باشد و یک Access Token صادر کند.

Access Token

Client ممکن است Token را به صورت Bearer ارسال کند:

Authorization: Bearer <token>

خود Access Token ممکن است JWT باشد:

Bearer
  ↓
JWT Access Token

اما لازم نیست JWT باشد؛ می‌تواند Opaque Token نیز باشد.

اگر Access Token کوتاه‌عمر باشد، Client ممکن است Refresh Token نیز داشته باشد:

Refresh Token
     ↓
Authorization Server
     ↓
New Access Token

اگر بخواهیم یک Application شخص ثالث به Resourceهای User دسترسی محدود داشته باشد، OAuth وارد ماجرا می‌شود.

اگر بخواهیم مسئله‌ی Login و Identity را استاندارد کنیم، OpenID Connect وارد ماجرا می‌شود.

و پس از Authentication، Authorization تعیین می‌کند User یا Client چه کارهایی اجازه دارد انجام دهد.


یک تصویر ذهنی کلی

می‌توان بخش مهمی از مفاهیم را این‌گونه در ذهن نگه داشت:

                    Authentication
                          │
            ┌─────────────┼─────────────┐
            │             │             │
        Password         MFA           OIDC
            │
            ▼
       User Verified
            │
      ┌─────┴─────┐
      │           │
   Session       Token
      │           │
   Cookie     ┌───┴─────┐
              │         │
          Opaque JWT    JWT
              │
              ▼
     Authorization: Bearer ...

و OAuth را می‌توان در یک محور دیگر دید:

OAuth
  │
  ▼
Delegated Authorization
  │
  ├── Access Token
  ├── Scope
  └── Third-party Access

اشتباهات رایج

در پایان، چند سوءبرداشت بسیار رایج را باید کنار بگذاریم.

JWT یک روش Login نیست.

JWT = Token Format

Bearer و JWT یک چیز نیستند.

Bearer = How a token is presented

JWT = A possible token format

OAuth به تنهایی معادل Login نیست.

OAuth = Delegated Authorization

OIDC برای Identity و Authentication روی OAuth ساخته شده است.

OIDC = Identity Layer

Cookie و Session یکی نیستند.

Cookie = Browser mechanism

Session = Server-side authentication state

HttpOnly به معنی ارسال نشدن Cookie نیست.

HttpOnly
→ JavaScript cannot normally read the cookie

Browser
→ Can still send it automatically

Refresh Token ذاتاً غیرقابل سرقت نیست.

Refresh Token نیز یک Credential حساس است و اگر لو برود می‌تواند بسیار خطرناک باشد.

دلیل وجود Access Token و Refresh Token این است که Credential پرتکرار و کوتاه‌عمر را از Credential حساس‌تر و کم‌استفاده‌تر جدا کنیم.


نکات امنیتی مهم

صرف انتخاب JWT، OAuth یا Cookie یک سیستم را امن نمی‌کند.

نحوه‌ی پیاده‌سازی اهمیت بسیار بیشتری دارد.

به طور کلی باید مواردی مثل این را در نظر گرفت:

  • همیشه برای اطلاعات Authentication و Tokenها از HTTPS استفاده شود.
  • Password به صورت Plain Text ذخیره نشود.
  • برای Password از Password Hashing مناسب استفاده شود.
  • Access Tokenها تا حد امکان کوتاه‌عمر باشند.
  • Refresh Tokenها با دقت بیشتری محافظت شوند.
  • Refresh Token بدون نیاز برای تمام API Endpointها ارسال نشود.
  • در صورت مناسب بودن از Refresh Token Rotation و Reuse Detection استفاده شود.
  • Scope و Permission بر اساس Principle of Least Privilege تعریف شوند.
  • Tokenها در Client در مکان نامناسب قرار نگیرند.
  • Cookieهای حساس در Web Applicationها بسته به معماری با ویژگی‌هایی مانند HttpOnly، Secure و SameSite پیکربندی شوند.
  • Path و Domain Cookieها بیش از حد گسترده تعریف نشوند.
  • برای حساب‌های حساس از MFA استفاده شود.
  • Logout، Revocation و Expiration از ابتدا جزئی از طراحی Authentication باشند، نه چیزی که در آخر پروژه اضافه شود.

جمع‌بندی

اگر بخواهیم کل بحث را در چند جمله خلاصه کنیم:

Authentication مشخص می‌کند شما چه کسی هستید.

Authorization مشخص می‌کند اجازه دارید چه کاری انجام دهید.

Username و Password یکی از ساده‌ترین روش‌های اثبات هویت هستند، اما Password باید به شکل امن Hash شود.

Session معمولاً وضعیت Login را سمت Server نگه می‌دارد و Cookie می‌تواند شناسه‌ی Session را در Browser نگهداری و ارسال کند.

Basic Auth Username و Password را در HTTP Authorization Header ارسال می‌کند و Base64 هیچ Encryption واقعی ایجاد نمی‌کند.

API Key معمولاً برای شناسایی Client، Application یا Project استفاده می‌شود.

Token-Based Authentication به جای ارسال مداوم Password از Token استفاده می‌کند.

Bearer روشی رایج برای ارائه‌ی Token است و دارنده‌ی Bearer Token می‌تواند از اعتبار آن استفاده کند.

Access Token برای دسترسی به Resource Server یا API است.

Refresh Token برای دریافت Access Token جدید است و باید بسیار محدودتر و محافظت‌شده‌تر استفاده شود.

JWT فقط یک Token Format است و Signed بودن آن به معنی مخفی بودن اطلاعات داخل آن نیست.

OAuth برای Delegated Authorization طراحی شده است.

OpenID Connect روی OAuth یک Identity Layer اضافه می‌کند و برای Authentication و Login استفاده می‌شود.

MFA با استفاده از چند Factor مستقل، وابستگی سیستم به یک Password را کاهش می‌دهد.

و شاید مهم‌ترین نکته این باشد:

امنیت Authentication از اسم تکنولوژی‌ای که انتخاب کرده‌ایم نمی‌آید؛ از معماری، نحوه‌ی نگهداری Credentialها، محدود کردن دسترسی، Expiration، Revocation و پیاده‌سازی صحیح آن می‌آید.