Menu

JWT ב-Node.js: חתימה, אימות והגנה על routes ב-Express

JWT הוא token חתום משלושה חלקים ב-base64url ששרת מנפיק בהתחברות ובודק בכל בקשה. ב-Node.js חותמים ומאמתים אותו עם החבילה jose או jsonwebtoken ומפתח סודי.

בדף הזה יש עורכים שאפשר להריץ. לערוך, להריץ ולראות את הפלט מיד.

JWT ‏(JSON Web Token) הוא מחרוזת משלושה חלקים ב-base64url, ‏header.payload.signature, ששרת נותן למשתמש בהתחברות ובודק בכל בקשה מאוחרת יותר. החתימה מוכיחה שהשרת הנפיק את ה-token ושאף אחד לא שינה אותו, אבל ה-payload רק מקודד, לא מוצפן, כך שכל אחד יכול לקרוא אותו. ב-Node.js יוצרים אחד עם new SignJWT(payload).sign(secret) מהחבילה jose ובודקים אותו עם jwtVerify(token, secret).

הוא מדפיס 3 parts, ואז user-42 admin: ‏jwtVerify בדק את החתימה ואת התוקף והחזיר את ה-payload. שנו תו אחד בסוד שמועבר ל-jwtVerify והריצו שוב: האימות נכשל עם signature verification failed.

מה יש בתוך JWT

ה-header וה-payload הם JSON, מקודדים ב-base64url. כדי לפענח אותם לא צריך מפתח, ובגלל זה JWT אף פעם לא צריך להחזיק סיסמה או כל דבר סודי אחר.

שתי השורות הראשונות מדפיסות {"alg":"HS256"} ו-{"name":"Ada","role":"editor"}, קריאים לכל מי שמחזיק את ה-token. החתימה (43 תווים ב-HS256) היא מה שמונע מהם לערוך אותו: שנו editor ל-admin והחתימה כבר לא תתאים.

חלקמכילסודי?
Headerהאלגוריתם, כמו HS256לא, קריא
Payloadclaims: ‏sub (מזהה המשתמש), exp (תפוגה), iat (מועד ההנפקה), שדות משלכםלא, קריא
Signatureחתימת HMAC או RSA של שני החלקים הראשוניםצריך את המפתח כדי ליצור אותה

תוקף ואימות שנכשל

setExpirationTime('15m') כותב claim בשם exp, ו-jwtVerify דוחה token שעבר אותו. כל דחייה היא שגיאה עם code, כך שאפשר להבדיל בין token שפג תוקפו לבין token מזויף.

הבדיקה הראשונה נכשלת עם ERR_JWT_EXPIRED והשנייה, שהחתימה שלה שונתה, עם ERR_JWS_SIGNATURE_VERIFICATION_FAILED. לגבי ה-API שלכם שתיהן אומרות אותו דבר (ענו 401), אבל רישום הקוד ללוג עוזר בדיבאג.

הגנה על route ב-Express עם Bearer token

הלקוח מתחבר פעם אחת, שומר את ה-token ושולח אותו ב-header ‏Authorization בתור Bearer <token>. middleware מאמת אותו ושם את ה-payload ב-req.user בשביל ה-route.

הבקשה הראשונה, GET /me בלי token, מקבלת 401 ו-Missing token. שלחו POST /login עם הגוף {"username": "ada", "password": "lovelace"}, העתיקו את ה-token מהתשובה, ואז שלחו שוב GET /me עם ה-header ‏Authorization: Bearer <token>: התשובה היא {"id":"user-1","name":"ada"}.

החבילה jsonwebtoken

jsonwebtoken (npm install jsonwebtoken) היא הבחירה הנפוצה השנייה. ה-sign וה-verify שלה סינכרוניים (עם callbacks אופציונליים) במקום להחזיר promises:

const jwt = require('jsonwebtoken');

const token = jwt.sign({ role: 'admin' }, process.env.JWT_SECRET, {
    subject: 'user-42',
    expiresIn: '1h',
});

const payload = jwt.verify(token, process.env.JWT_SECRET); // throws if invalid

השתמשו ב-jose בקוד חדש: אין לה תלויות, והיא רצה גם ב-edge runtimes ובדפדפנים. tokens משתי הספריות הם JWTs סטנדרטיים, כך שאחת יכולה לאמת את מה שהשנייה חתמה.

טעויות נפוצות

שימוש ב-decode במקום ב-verify. jwt.decode ו-decodeJwt קוראים את ה-payload בלי לבדוק את החתימה, כך ש-token מזויף עובר. סמכו רק על מה ש-verify מחזיר.

סוד קצר או סוד שעשו לו commit. סוד HS256 שאפשר לנחש מאפשר לתוקפים לחתום tokens משלהם. השתמשו לפחות ב-32 בייטים אקראיים, קראו אותם מ-process.env, ואל תעשו להם commit אף פעם.

tokens שלא פגים אף פעם. בלי exp, ‏token שדלף עובד לנצח. תמיד הגדירו תפוגה.

שאלות נפוצות

איפה הלקוח צריך לשמור JWT?

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

איך מנתקים משתמש כשעובדים עם JWTs?

השרת לא יכול לבטל token שהוא כבר חתם; הוא נשאר תקף עד שהתוקף שלו פג. מחקו אותו בצד הלקוח, תנו ל-access tokens תוקף קצר, ואם אתם צריכים ניתוק מיידי, החזיקו בשרת רשימה של ids של tokens שבוטלו (jti) ובדקו אותה בכל בקשה.

HS256 או RS256?

HS256 משתמש בסוד משותף אחד לחתימה ולאימות, וזה מתאים ל-backend יחיד. RS256 חותם עם מפתח פרטי ומאמת עם מפתח ציבורי, כך ששירותים אחרים יכולים לבדוק tokens בלי שיוכלו ליצור אותם.

כמה זמן JWT צריך להיות תקף?

access tokens תקפים בדרך כלל בין 5 ל-60 דקות. לסשנים ארוכים יותר, צרפו אליהם refresh token שנשמר בזהירות רבה יותר, שהלקוח מחליף ב-access token חדש.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל