לדלג לתוכן

בונים מערכת התחברות עם JWT - הפרויקט שמלמד אתכם אבטחה אמיתית

מערכת התחברות (authentication) היא אחד הפרויקטים המשמעותיים ביותר שאפשר לבנות כמתחילים, כי היא לא רק תרגול טכני - היא הפעם הראשונה שבה טעות בקוד שלכם יכולה לחשוף מידע של משתמשים אמיתיים. זה הופך אותה לפרויקט מצוין ללמידה, אבל גם כזה שדורש תשומת לב לפרטים, ולא רק "שזה יעבוד".

מה זה בעצם JWT

JWT, ראשי תיבות של JSON Web Token, הוא בעצם מחרוזת מוצפנת שמכילה מידע על המשתמש - למשל את ה-id שלו - וחתומה בעזרת מפתח סודי שרק השרת מכיר. כשמשתמש מתחבר בהצלחה, השרת מנפיק לו טוקן כזה, והמשתמש שולח אותו בכל בקשה עתידית כדי להוכיח מי הוא, בלי שהשרת צריך לשמור session בזיכרון. זה מה שהופך את JWT לפופולרי כל כך ב-API-ים מודרניים - הוא stateless, כלומר השרת לא צריך לזכור כלום בין בקשה לבקשה.

שלב ראשון - לעולם לא שומרים סיסמה כמו שהיא

הכלל הכי חשוב לפני שכותבים שורת קוד אחת - סיסמה של משתמש לעולם לא נשמרת במסד הנתונים כמו שהיא (plain text). אם מישהו יפרוץ למסד הנתונים שלכם, אתם לא רוצים שהוא יקבל את הסיסמאות של כל המשתמשים בטקסט גלוי. הפתרון הוא hashing עם ספרייה כמו bcrypt:

const bcrypt = require("bcrypt");
const hashedPassword = await bcrypt.hash(password, 10);

המספר 10 הוא מספר הסבבים (salt rounds) - ככל שהוא גבוה יותר, ההצפנה איטית יותר ובטוחה יותר נגד ניסיונות פיצוח. כשמשתמש מתחבר, לא מפענחים את ה-hash חזרה לסיסמה - זה בלתי אפשרי בכוונה - אלא משווים בעזרת bcrypt.compare.

הנפקת טוקן

אחרי שהסיסמה אומתה, השרת מנפיק JWT:

const jwt = require("jsonwebtoken");
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: "1h" });

שימו לב לשני דברים כאן. ראשית, ה-secret נשמר במשתנה סביבה (process.env) ולא כמחרוזת קבועה בקוד - זו טעות נפוצה מאוד אצל מתחילים, שמעלים בטעות את המפתח הסודי ל-GitHub. שנית, יש תוקף (expiresIn) לטוקן. טוקן שלא פג לעולם הוא סיכון - אם הוא ידלוף, מי שתפס אותו יכול להתחזות למשתמש לצמיתות.

הגנה על נתיבים עם middleware

ברגע שיש טוקן, צריך דרך לבדוק אותו בכל בקשה לנתיב מוגן. זה נעשה עם middleware שרץ לפני ה-handler עצמו:

function auth(req, res, next) {
  const token = req.headers.authorization?.split(" ")[1];
  if (!token) return res.status(401).json({ error: "No token provided" });

  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch {
    res.status(401).json({ error: "Invalid token" });
  }
}

עכשיו כל נתיב שרוצים להגן עליו פשוט מוסיף את ה-middleware הזה: app.get("/profile", auth, handler). זה דפוס שחוזר על עצמו בכמעט כל API עם הרשאות, ושווה להבין אותו לעומק ולא רק להעתיק.

טעויות אבטחה נפוצות שכדאי להכיר

שמירת טוקן במקום הלא נכון בצד לקוח. שמירת JWT ב-localStorage נוחה, אבל חושפת אותו להתקפות XSS - קוד זדוני שרץ בדף יכול לגנוב אותו. באפליקציות רגישות עדיף שימוש ב-cookie עם דגלי HttpOnly ו-Secure.

מפתח סודי חלש. secret שהוא המילה "123456" הוא לא הגנה, הוא הזמנה. מפתח JWT צריך להיות ארוך, אקראי, ושמור במשתני סביבה, לא בקוד.

אין תוקף לטוקן, או תוקף ארוך מדי. טוקן שחי לנצח הוא נקודת כשל בודדת - די בדליפה אחת כדי לאבד שליטה לצמיתות.

מילה על refresh tokens

בפרויקטים אמיתיים, לרוב לא מסתפקים בטוקן יחיד. מנפיקים access token קצר טווח (למשל שעה) יחד עם refresh token ארוך טווח יותר, ששמור בצורה בטוחה יותר ומשמש רק כדי להנפיק access token חדש כשהישן פג. זה מאזן בין נוחות המשתמש - שלא צריך להתחבר מחדש כל שעה - לבין הסיכון של טוקן שדולף. זה לא חובה בפרויקט הראשון שלכם, אבל שווה להכיר את הרעיון לפני שעוברים לעולם האמיתי.

למה שווה לבנות את זה בעצמכם

מערכת התחברות היא לא רק "עוד פיצ'ר" - היא מקום מצוין להבין לעומק איך מנגנוני אבטחה עובדים מתחת למכסה המנוע, במקום להתייחס אליהם כקסם. זה בדיוק סוג הידע שמבדיל מפתח שמעתיק קוד מבין מפתח שמבין למה הוא כותב אותו.

אנחנו מלמדים את כל הנושא הזה לעומק, כולל התרגילים המעשיים והמלכודות הנפוצות, בקורס צד שרת המלא, עם דוגמאות אמיתיות ולא רק תיאוריה.

יש לכם שאלה על אבטחה בפרויקט שאתם בונים? שאלו בקהילה שלנו בדיסקורד ותקבלו תשובה מאנשים שכבר התמודדו עם זה.

הצטרפו לקהילה בדיסקורד