TrendVidStream העבירה את כל מחסנית האימות שלה למערכת rotating-refresh-token שמזהה token גנוב ברגע שהוא מנוצל מחדש. השינוי הפך JWT בעל תוקף של 30 יום, שבעבר אפשר לתוקף לשוטט בחופשיות, להרשאה קצרת-טווח שמפעילה באופן מיידי נעילה (lock-out).
פריצה בודדת אילצה את המעבר: SDK של שותף שמר בזיכרון המטמון (cached) JWT של 30 יום כטקסט גלוי (plain text), תוקף חילץ אותו והשתמש בו מחדש (replayed) ממדינה אחרת, והפתרון היחיד היה לסובב (rotate) את מפתח החתימה – פעולה שגרמה לכל המשתמשים להתנתק. האירוע עיצב מחדש את אבטחת ה-tokens של החברה וכעת הוא מפעיל את שירות הזרמת הווידאו.
Why the old model failed
JWTs (JSON Web Tokens) הם אובייקטים (blobs) חתומים ועצמאיים המאפשרים לשרת לאמת בקשה ללא צורך בשאילתה למסד הנתונים. הנוחות הזו מסתירה מחיר: אם ל-token יש תוקף של שבועות, גניבתו מעניקה ליריב גישה למשך שבועות. בפריצה, ה-token הגנוב נותר תקף עד לפקיעת תוקפו לאחר 30 יום, מכיוון שלא הייתה דרך לבטל את תוקפו מוקדם יותר.
סיבוב מפתח החתימה הוא הדרך הגלובלית היחידה לבטל את כל ה-tokens, אך הוא מאלץ כל משתמש להתחבר מחדש, מה שמשבש את השירות ופוגע באמון. הפגם לא היה ב-JWT עצמו, אלא בהסתמכות על הרשאה יחידה בעלת תוקף ארוך.
The new design in a nutshell
TrendVidStream מנפיקה כעת שני סוגים של tokens:
- Access tokens – תקפים ל-15 דקות; הם נושאים את ההרשאות הדרושות לכל קריאת API.
- Refresh tokens – tokens לשימוש חד-פעמי המחליפים access token קצר-טווח בזוג חדש.
כאשר לקוח מציג refresh token, השרת:
- מאמת את החתימה וה-claims של ה-token.
- בודק האם ה-token כבר נוצל.
- אם הבדיקה עוברת, מנפיק refresh token חדש לגמרי ו-access token חדש לתקופה של 15 דקות.
- מסמן את ה-refresh token הישן כמשומש.
אם שלב 2 נכשל – כלומר, אותו token מופיע בפעם השנייה – השרת מתייחס לכך כאות לגניבה ומבטל את כל "משפחת" ה-tokens הקשורה לסשן ההתחברות הזה. גם הקורבן וגם התוקף נאלצים להתחבר מחדש, מה שמקצר את משך הפריצה.
Tracking token families
במקום להתייחס לכל token כרשומה מבודדת, המערכת מקבצת אותם למשפחות המתחילות ברגע שמשתמש מתחבר. כל rotation יוצר חבר חדש באותה משפחה. סכימת ה-SQLite המשמשת בייצור (production) שומרת:
- family_id – מזהה יציב לכל הסשן.
- token_id – מזהה ייחודי לכל refresh token.
- generation – מונה (counter) שימושי לניפוי שגיאות (debugging).
- used_at – חותמת זמן (timestamp) של מועד מימוש ה-token.
- revoked – דגל (flag) שברגע שמוגדר, מבטל את המשפחה כולה.
כאשר מזוהה אירוע שימוש חוזר, דגל ה-revoked עבור אותו family_id מוגדר, מה שמבטל באופן מיידי כל token השייך לסשן שנפרץ. זה הופך חטיפה שקטה להתראה בעלת אות גבוה (high-signal alert) המופיעה בלוגים.
Three implementation rules that keep the system reliable
Validate the signature first תוקף יכול לנחש token IDs ולגרום לביטולים המוניים אם השרת בודק "האם כבר נוצל?" לפני אימות החתימה. אימות האותנטיות תחילה מונע התקפות DoS (denial-of-service) מיותרות.
Allow a grace window אפליקציות מובייל שולחות לעיתים קרובות שתי בקשות refresh ברצף מהיר כאשר token פוקע. אם השרת יהיה קפדני מדי, הבקשה השנייה תסומן כשימוש חוזר, מה שיוביל להתנתקות של משתמש לגיטימי. כמה שניות של buffer מאפשרות ל-token "משומש" עדיין להחזיר זוג חדש, ובכך מונעות race conditions.
Wrap the whole flow in a transaction with row locking ללא אטומיות (atomicity), שתי בקשות בו-זמניות עלולות לחשוב ששתיהן הראשונות המשתמשות ב-token, מה שינפיק refresh tokens כפולים וישבור את ההבטחה לשימוש חד-פעמי. טרנזקציה במסד הנתונים הנועלת את שורת ה-token מבטיחה שרק בקשה אחת תצליח.
Takeaway: על ידי הפיכת refresh tokens לחד-פעמיים ומעקב אחר שימוש חוזר, מערכת יכולה להפוך כל הרשאה גנובה להתראה, ובכך להגן על משתמשים מבלי לאלץ התנתקות של כל הפלטפורמה.
