راهنمای 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 چیست؟
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 و Cookie یکی نیستند
این نکته اهمیت زیادی دارد.
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
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 و پیادهسازی صحیح آن میآید.