`Cookie` ב-TypeScript
מאמר זה מסביר על Cookie ב-TypeScript.
נעבור על דפוסים מעשיים לטיפול בעוגיות בצורה בטוחה ואמינה הן בדפדפן והן בשרת.
YouTube Video
Cookie ב-TypeScript
מושגי יסוד בעוגיות
עוגייה היא מנגנון לאחסון מחרוזות קצרות (זוגות name=value) בצד הלקוח, הנוצר באמצעות כותרת ה-HTTP Set-Cookie או באמצעות document.cookie. ניתן לשלוט בהתנהגותן באמצעות מאפייני אבטחה (HttpOnly, Secure, SameSite וכו').
פעולות בסיסיות בדפדפן: document.cookie
להלן דוגמה מינימלית לכתיבת עוגייה בדפדפן. צרו עוגייה על ידי הוספת מחרוזת אל document.cookie.
1// Set a simple cookie that expires in 7 days.
2// Note: Comments are in English per the user's preference.
3const setSimpleCookie = (name: string, value: string) => {
4 const days = 7;
5 const expires = new Date(Date.now() + days * 86400_000).toUTCString();
6 // name=value; Expires=...; Path=/
7 document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; Expires=${expires}; Path=/`;
8};
9
10setSimpleCookie('theme', 'dark');- קוד זה יוצר עוגייה שפגה לאחר 7 ימים. קל להשתמש בו בצד הדפדפן, אך מאחר שלא ניתן להגדיר
HttpOnly, יש להימנע מאחסון מידע רגיש.
קריאת עוגיות (דפדפן)
הפונקציה הבאה היא פונקציית עזר שמאחזרת את ערך העוגייה בעלת שם מסוים מתוך document.cookie. document.cookie מוחזר כמחרוזת שמופרדת באמצעות נקודה-פסיק ורווח ('; '). לכן אנו מפצלים אותה ומחפשים את העוגייה הרצויה.
1// Parse document.cookie and return value for given name or null if not found.
2const getCookie = (name: string): string | null => {
3 const cookies = document.cookie ? document.cookie.split('; ') : [];
4 for (const cookie of cookies) {
5 const [k, ...rest] = cookie.split('=');
6 const v = rest.join('=');
7 if (decodeURIComponent(k) === name) {
8 return decodeURIComponent(v);
9 }
10 }
11 return null;
12};
13
14// Example usage:
15const theme = getCookie('theme'); // => "dark" if set
16console.log('theme cookie:', theme);- הפונקציה הזאת מפענחת גם את שם העוגייה וגם את ערכה כדי להשוות ולאחזר אותם בבטחה. היא מטפלת בשמות עוגיות כפולים ובתווים מיוחדים מקודדים, מה שהופך אותה למימוש פשוט אך עמיד.
הבנה ויישום של מאפייני אבטחה
לעוגיות יש כמה מאפיינים חשובים, שכל אחד מהם שולט באבטחה ובתחום הפעולה.
- המאפיין
HttpOnlyמונע מהעוגייה להיות נגישה מ-JavaScript, ובכך מסייע להפחית מתקפות XSS (Cross‑Site Scripting). שימו לב שלא ניתן להגדירHttpOnlyמצדו של הדפדפן. - המאפיין
Secureמגביל שליחת עוגיות ל-HTTPS בלבד, ומפחית את הסיכון להאזנה ולשינוי זדוני. - המאפיין
SameSiteקובע האם עוגיות יישלחו בבקשות חוצות-אתרים, ומסייע למנוע מתקפות CSRF (Cross‑Site Request Forgery). - המאפיין
Pathמגדיר את תחום נתיבי הבקשות שעבורם נשלחת העוגייה, והמאפייןDomainמגדיר את הדומיין שבו העוגייה תקפה. - באמצעות הגדרת
ExpiresאוMax-Ageניתן לשלוט בתפוגת העוגייה.
דוגמה להוספת SameSite / Secure בדפדפן (מה שאפשרי)
לא ניתן להגדיר HttpOnly בצד הלקוח. להלן דוגמה להוספת SameSite ו-Secure בצד הלקוח. עם זאת, Secure נכנס לתוקף רק בדפי HTTPS.
1// Set cookie with SameSite and Secure attributes (Secure only effective over HTTPS).
2const setCookieWithAttributes = (name: string, value: string) => {
3 const maxAge = 60 * 60 * 24 * 7; // 7 days in seconds
4 // Note: HttpOnly cannot be set from JS; set it on server-side when you want to restrict JS access.
5 document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; `
6 + `Max-Age=${maxAge}; `
7 + `Path=/; `
8 + `SameSite=Lax; `
9 + `Secure`;
10};
11
12setCookieWithAttributes('session_hint', 'true');SameSite=Laxהוא בחירה בטוחה ונפוצה. למגבלות מחמירות יותר השתמשו ב-SameSite=Strict, אך ניווטים לגיטימיים מאתרים חיצוניים עלולים להפסיק לעבוד.
הגדרת עוגיות בשרת (Set-Cookie) (Node / TypeScript)
בשרת ניתן להוסיף HttpOnly באמצעות כותרת Set-Cookie, ולכן ניהול סשנים רצוי שייעשה בצד השרת. להלן דוגמה באמצעות המודול http של Node.
1// A minimal Node HTTP server in TypeScript that sets a secure HttpOnly cookie.
2// This example uses built-in 'crypto' for a random session id.
3import http from 'http';
4import crypto from 'crypto';
5
6const server = http.createServer((req, res) => {
7 if (req.url === '/login') {
8 const sessionId = crypto.randomBytes(16).toString('hex');
9 // Set cookie with HttpOnly, Secure, SameSite and Path
10 // Expires is optional — Max-Age preferred for relative lifetimes.
11 res.setHeader('Set-Cookie', `sid=${sessionId}; `
12 + `HttpOnly; `
13 + `Secure; `
14 + `SameSite=Strict; `
15 + `Path=/; `
16 + `Max-Age=3600`
17 );
18 res.writeHead(302, { Location: '/' });
19 res.end();
20 return;
21 }
22
23 res.writeHead(200, { 'Content-Type': 'text/plain' });
24 res.end('Hello\n');
25});
26
27server.listen(3000, () => {
28 console.log('Server running on http://localhost:3000');
29});- כאשר מגדירים את
HttpOnlyבצד השרת, העוגייה אינה נגישה מ-JavaScript, מה שמקשה על מתקפות XSS (Cross‑Site Scripting) לגנוב אותה. בנוסף, על ידי הוספתSecureכך שעוגיות יישלחו תמיד דרך HTTPS, ניתן למנוע האזנה ושינוי זדוני ולשפר את אבטחת התקשורת.
מימוש סיריאליזציה/פענוח של עוגיות (עזר בצד השרת)
להלן כלי עזר פשוט בצד השרת שבונה מחרוזת כותרת Set-Cookie.
1// Cookie serialization helper for server-side use.
2// Returns a properly formatted Set-Cookie header value.
3type CookieOptions = {
4 path?: string;
5 domain?: string;
6 maxAge?: number;
7 expires?: Date;
8 httpOnly?: boolean;
9 secure?: boolean;
10 sameSite?: 'Strict' | 'Lax' | 'None';
11};
12
13const serializeCookie = (name: string, value: string, opts: CookieOptions = {}): string => {
14 const parts: string[] = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
15
16 if (opts.maxAge != null) parts.push(`Max-Age=${Math.floor(opts.maxAge)}`);
17 if (opts.expires) parts.push(`Expires=${opts.expires.toUTCString()}`);
18 if (opts.domain) parts.push(`Domain=${opts.domain}`);
19 parts.push(`Path=${opts.path ?? '/'}`);
20 if (opts.httpOnly) parts.push('HttpOnly');
21 if (opts.secure) parts.push('Secure');
22 if (opts.sameSite) parts.push(`SameSite=${opts.sameSite}`);
23
24 return parts.join('; ');
25};
26
27// Example usage:
28const headerValue = serializeCookie('uid', 'abc123', {
29 httpOnly: true,
30 secure: true,
31 sameSite: 'Lax',
32 maxAge: 3600
33});
34console.log(headerValue);
35// => "uid=abc123; Max-Age=3600; Path=/; HttpOnly; Secure; SameSite=Lax"
- כלי עזר זה מהווה בסיס ליצירת
Set-Cookieבשרת מותאם אישית. ספריות יכולות לטפל בעוד מקרים קיצוניים רבים.
מניעת שינוי ערכים באמצעות חתימות (HMAC)
מסוכן לאחסן ערכים חשובים ישירות בעוגיות. נציג שיטה שחותמת ערכים בשרת כדי לזהות שינוי. כאן נשתמש ב-HMAC-SHA256. בסביבת ייצור, יש לנהל גם שלילת טוקנים בצד השרת.
1// A simple HMAC signing and verification helper for cookies.
2// Signing prevents client from tampering cookie values.
3import crypto from 'crypto';
4
5const SECRET = 'replace_with_env_secret'; // store in env var in production
6
7const signValue = (value: string) => {
8 const hmac = crypto.createHmac('sha256', SECRET);
9 hmac.update(value);
10 return `${value}.${hmac.digest('hex')}`;
11};
12
13const verifySignedValue = (signed: string): string | null => {
14 const idx = signed.lastIndexOf('.');
15 if (idx === -1) return null;
16 const value = signed.slice(0, idx);
17 const sig = signed.slice(idx + 1);
18
19 const hmac = crypto.createHmac('sha256', SECRET);
20 hmac.update(value);
21 const expected = hmac.digest('hex');
22 // use timing-safe comparison in production
23 if (crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
24 return value;
25 }
26 return null;
27};
28
29// Example usage:
30const signed = signValue('userId=42');
31console.log('signed:', signed);
32console.log('verified:', verifySignedValue(signed)); // => "userId=42"
- באמצעות שיטה זו ניתן לבדוק אם ערך ה-Cookie שונה. אין לכלול את મפתח החתימה בקוד המקור; יש לנהל אותו באמצעים מאובטחים כגון משתני סביבה.
עוגיות וצמצום CSRF (דפוסים כלליים)
סשנים המשתמשים בעוגיות זקוקים להגנת CSRF. אמצעי מיתון טיפוסיים כוללים:.
- הגדירו
SameSiteל-LaxאוStrictכדי למנוע שליחה בין-אתרית מיותרת. השתמשו ב-SameSite=None; Secureרק כאשר תקשורת בין-מקורות נחוצה. - השתמשו באסימוני CSRF, המאומתים על ידי השרת, עבור טפסים ובקשות API. אל תאחסנו את האסימון בעוגיית
HttpOnly; במקום זאת, ספקו אותו דרך גוף התגובה או תגיות meta, ותנו ל-JavaScript לקרוא אותו רק בעת הצורך. - אם ניתן לגשת לאסימון מ-JavaScript, ודאו שגם מיושמות הגנות מפני XSS (CSP, אסקייפינג של פלט וכו׳).
- דרשו אימות מחדש עבור פעולות רגישות ובכניסה, ומנעו מתקפות קיבוע מושב.
להלן דוגמה ללקוח מאובטח ששולח את אסימון ה-CSRF בכותרת. זה מניח שהלקוח כבר קיבל את האסימון מהשרת, למשל בגוף התגובה.
1// Example of sending CSRF token safely in a header using fetch.
2// Assumes CSRF token was provided securely (e.g., via response body or meta tag).
3async function sendProtectedRequest(url: string, csrfToken: string) {
4 const res = await fetch(url, {
5 method: 'POST',
6 credentials: 'same-origin', // include cookies for same-site requests
7 headers: {
8 'Content-Type': 'application/json',
9 'X-CSRF-Token': csrfToken
10 },
11 body: JSON.stringify({ action: 'doSomething' })
12 });
13 return res;
14}- אם מציינים
credentials: 'same-origin', נשלחות רק עוגיות עבור אותו מקור. בשרת, אמתו את ערך כותרתX-CSRF-Tokenואשרו שהאסימון תואם. אם בקשות בין-אתריות נחוצות, הגדירו את CORS בקפידה.
בין-אתרי (CORS) והקשר ל-credentials
כדי לשלוח ולקבל עוגיות בתקשורת בין-מקורות, הלקוח חייב לציין credentials: 'include' והשרת חייב להגדיר Access-Control-Allow-Credentials: true. עם זאת, הגבילו תצורה זו למקורות מהימנים ואל תשתמשו ב-Access-Control-Allow-Origin: *.
1async function fetchCrossOriginData() {
2 // Example: cross-origin fetch sending cookies.
3 // Server must set Access-Control-Allow-Credentials: true
4 // and a specific trusted origin.
5 const res = await fetch('https://api.example.com/data', {
6 credentials: 'include', // send cookies only to trusted domains
7 method: 'GET'
8 });
9 return res;
10}- השילוב בין CORS לעוגיות רגיש מאוד מבחינת אבטחה. נהלו בקפדנות את המקורות המותרים באמצעות רשימת מורשים (whitelist) והימנעו מתקשורת בין-אתרית מיותרת. בנוסף, בעת שימוש ב-
SameSite=None; Secure, אכפו HTTPS כדי למנוע מתקפות אדם-בתווך.
דוגמה: הגדרות עוגיית סשן מאובטחות עם Express (TypeScript)
בפועל, השתמשו ב-Express, בספריית cookie, ב-express-session וכדומה. להלן דוגמה פשוטה באמצעות express ו-cookie-parser. בפועל, הגדירו secure ל-true ונהלו את ה-secret באמצעות משתני סביבה.
1// Express example using cookie-parser and setting a secure httpOnly cookie.
2// npm install express cookie-parser @types/express @types/cookie-parser
3import express from 'express';
4import cookieParser from 'cookie-parser';
5
6const app = express();
7app.use(cookieParser(process.env.COOKIE_SECRET));
8
9app.post('/login', (req, res) => {
10 // authenticate user (omitted)
11 const sessionId = 'generated-session-id';
12 res.cookie('sid', sessionId, {
13 httpOnly: true,
14 secure: true, // require HTTPS in production
15 sameSite: 'lax',
16 maxAge: 1000 * 60 * 60 // 1 hour
17 });
18 res.json({ ok: true });
19});
20
21app.listen(3000);- באמצעות התכונה
secretשלcookieParser, ניתן לעבוד בקלות עם עוגיות חתומות. אולם, ביישומים אמיתיים, מבחינות של אבטחה וסקיילביליות, אין לאחסן נתונים ישירות בעוגיות; במקום זאת, השתמשו במאגר סשן ייעודי.
קידומות עוגיות __Host- ו-__Secure-
דפדפנים אוכפים כללים מיוחדים עבור קידומות מסוימות.
- הקידומת
__Secure-אם שם העוגייה מתחיל ב-__Secure-, המאפייןSecureנדרש. - הקידומת
__Host-אם הוא מתחיל ב-__Host-, יש לדרושSecure, הנתיב חייב להיות/(שורש), ואסור להגדירDomain.
השימוש בקידומות הללו מפחית טעויות תצורה ומשפר את האבטחה.
נהלים מומלצים לעוגיות
כדי לטפל בקובצי Cookie באופן מאובטח, יש לשקול את הנקודות הבאות.
- אל תאחסנו מידע רגיש ישירות בעוגיות. העדיפו מזהה סשן בצד השרת עבור אסימוני גישה.
- הגדירו עוגיות סשן עם
HttpOnly, Secureו-SameSite=Lax(או Strict). - נצלו קידומות כמו
__Host-ו-__Secure-. - שקלו חתימות (HMAC) והצפנה כדי למנוע שינוי והאזנה.
- הפעילו
Secureודרשו HTTPS. - שאפו לעיקרון המינימום הנדרש ולזמני תפוגה קצרים.
- השתמשו באסימוני CSRF.
- היזהרו מהבדלים בהתנהגות
SameSiteבין דפדפנים, במיוחד ישנים.
טעויות נפוצות לגבי עוגיות
בנוגע לעוגיות, שימו לב לתפיסות השגויות הנפוצות הבאות.
- 'הוספת
HttpOnlyמבטלת את השפעת ה-XSS.' אמנםHttpOnlyמונע גישה של JavaScript לעוגיות, אך ניתן עדיין לנצל XSS כדי לבצע בקשות שרירותיות. יש ליישם גם אימות טוקן CSRF, CSP (מדיניות אבטחת תוכן), וטיהור קלט. - '
Secureמיותר לפיתוח מקומי.' סימולציה של HTTPS ואימות ההתנהגות גם בסביבות מקומיות משפרים את דיוק הבדיקות. לכל הפחות, יש להשתמש ב-HTTPS בסביבות Staging ו-Production. - 'תקופות תפוגה ארוכות הן נוחות.' אם תקבעו לעוגיות משך חיים ארוך, מתארכת התקופה שבה ניתן יהיה לנצלן אם ייגנבו. ניתן להגדיר תפוגות קצרות יותר ולשלב אימות חוזר תקופתי ורוטציית אסימונים.
סיכום
למרות שעוגיות קלות לשימוש, טיפול לקוי בהן עלול להכניס פגיעויות אבטחה. כדי לנהל אותן כראוי עם TypeScript, חשוב להבין מאפיינים כמו HttpOnly, Secure ו-SameSite, ולאכוף הגדרות מאובטחות בצד השרת. באמצעות הימנעות מאחסון ישיר של נתונים רגישים ושילוב חתימות עם זמני תפוגה קצרים, ניתן להשיג ניהול סשן בטוח ואמין.
תוכלו לעקוב אחר המאמר שלמעלה באמצעות Visual Studio Code בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.