לדלג לתוכן

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

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

אימות מול הרשאה - שני דברים שונים לגמרי

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

הבלבול הנפוץ הוא לחשוב שברגע שיש לוגין, הבעיה נפתרה. זה לא נכון. כל endpoint בשרת צריך לשאול שתי שאלות נפרדות - מי פונה אליי, ומותר לו לעשות את מה שהוא מבקש.

איך מתכננים סכמת תפקידים והרשאות

הדגם הפשוט והנפוץ ביותר הוא RBAC - הרשאות מבוססות תפקיד. במקום לתת לכל משתמש רשימת הרשאות משלו, מגדירים תפקידים קבועים - למשל admin, editor, viewer - וכל תפקיד מקבל קבוצת הרשאות. משתמש מקבל תפקיד אחד או יותר, ובכך יורש את כל ההרשאות שלו.

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

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

Middleware שבודק הרשאה בכל בקשה

ברגע שיש סכמה, צריך לאכוף אותה בכל בקשה שמגיעה לשרת, לא רק כשנוח. הדרך הנקייה לעשות את זה היא middleware שרץ לפני שה-handler של ה-route בכלל מתחיל לעבוד:

function requirePermission(permission) {
  return (req, res, next) => {
    const userPermissions = getPermissionsForUser(req.user);
    if (!userPermissions.includes(permission)) {
      return res.status(403).json({ error: "אין הרשאה" });
    }
    next();
  };
}

router.delete("/users/:id", requirePermission("users:delete"), deleteUserHandler);

היתרון של גישה כזאת הוא שהבדיקה לא תלויה בזיכרון של מי שכתב את ה-handler הספציפי. היא חלק מהגדרת ה-route עצמו, וקשה לשכוח אותה בטעות.

מוקש מספר 1 - לבדוק הרשאות רק בצד לקוח

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

מוקש מספר 2 - ברירת מחדל מורשית מדי

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

מה זה נותן לכם בפועל

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

איפה בונים את זה בפועל

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

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

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