توکن رو کجا ذخیره کنیم؟ کوکی یا LocalStorage؟ چرا و چطور؟
وقتی صحبت از احراز هویت توی صفحه های SPA میشه، خیلی وقتا این سوال مطرح میشه که برای اینکه بعد از بستن مرورگر نیاز به توکن احراز هویت رو کجا ذخیره کنیم؟ معمولا این ۲ گزینه ها جز مهم ترین انتخاب ها هستن:
- ذخیره کردن توی Local Storage یا محل های مشابه
- ذخیره کردن توی کوکی
چیزی توی اکثر پیاده سازی ها میبینم،استفاده از Local Storage هست. به اینصورت که توکن رو یا توی state ذخیره میکنن و کل state رو میزارن توی Local Storage و یا مستقیم خوده token رو توی Local Storage ذخیره میکنن. خب با این روش، صفحه داره کار میکنه، اما آیا این روش امن هست ؟
نه، این روش(نسبتاً) امن نیست! دلیلش اینه که اگر توی صفحه وب شما، باگ XSS وجود داشته باشه، مهاجم میتونه به راحتی مقادیر موجود در Local Storage شمارو بخونه و برای خودش ارسال کنه.
خب اگه بخوایم در برابر این حمله مقاوم باشیم، باید از یچیزی استفاده کنیم که Javascript نتونه بهش دسترسی داشته باشه !
جواب: استفاده از کوکی های HttpOnly
اگر از کوکی های معمولی استفاده کنیم، برای کارِ ما آنچنان فرقی با برای ما نداره چون توسط Javascript قابل دسترسی هست و برمیگردیم به پله اول. اما کوکی ها یه فلگ دارن به اسم HttpOnly که راه چاره ماست
فلگ HttpOnly توی کوکی چیه؟
این فلگ نشون دهنده اینه که کوکی فقط توی درخواست های (s)Http ارسال میشه و دیگه توسط Javascript قابل دیدن یا نوشته شدن نیست. یعنی در برابر حملات XSS مقاوم میشه.
اگه نمیشه با Javascript بهش دسترسی داشت چطوری ازش استفاده کنیم؟
برای اینکه بخواین یه کوکی رو توی مرورگر تنظیم کنیم ۲ تا راه هست:
- کوکی رو با جاوا اسکریپت توی مرورگر تنظیم کنیم
- کوکی رو از سمت سرور با هدر set-cookie تنظیم کنیم
کوکی هایی که از نوع HttpOnly باشن فقط و فقط از طریق سرور قابل خوندن و نوشتن هستن. پس کافیه کارِ احراز هویت رو به سرور بسپاریم.
برای مثال، من اینکار رو با express به اینصورت پیاده کردم:
const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.use(cookieParser()); // TODO: this should be generated and validated properly (via jwt for example) const secretToken = "MY_SECRET_RANDOM_GENERATED_TOKEN"; /** * Login route - Currently no real authentication is involved, allow everyone to login */ app.get('/login', (req, res)=>{ // TODO: Do some real authentication then set the httpOnly cookie const cookieName = 'access_token'; const cookieValue = secretToken; const cookieOptions = { httpOnly: true, expires: new Date(Date.now() + 3600000 * 24 * 365 ) } return res .cookie(cookieName, cookieValue, cookieOptions) .status(201) .send('Done'); }); /** * This route checks whether we are authenticated or not */ app.get('/test', (req, res)=>{ const isAuthenticated = req.cookies['access_token'] === secretToken; return res.send(`You are ${ isAuthenticated ? '' : 'not' } authenticated`); }); const expressPort = 3085; app.listen(expressPort, () => { console.log(`Running on ${expressPort}`) });
هر وقت به مسیر /login یه درخواست get بزنیم، کوکی برای ما تنظیم میشه (مهم نیست با ajax باشه یا نه). و برای چک کردنه اینکه کوکی سمت سرور قابل خوندن هست یا نه، کافیه به مسیر /test یه درخواست بزنیم.
خب الان من با مرورگرم به مسیر login رفتم و دیدم که کوکی با موفقیت برای من ست شده:

اما اگر از طریق console، بخوام کوکی هارو بخونم، هیچ مقداری قابل دیدن نیست:

برای تست نهایی، کافیه به مسیر test برم و مطمئن بشم که سرور میتونه کوکی رو بخونه:

و کار تمام شد. پس دیدیم که به سادگی میشه توکن رو توی کوکی ذخیره کرد، بدون اینکه Javascript بهش دسترسی داشته باشه.
همچنین برای تامین امنیت بیشتر توصیه میشه موارد زیر خونده بشه:
22
پاسخ ها