שאלות מראיונות עבודה: אימות קלט בצד הלקוח

פוסט זה כולל טיפ קצר בנושא פיתוח Front End. אם אתם רוצים ללמוד יותר לעומק על פיתוח Front End מהבסיס ועד הנושאים המתקדמים תשמחו לשמוע שבניתי קורס וידאו מקיף בנושא זה הכולל מעל 50 שיעורי וידאו והמון תרגול מעשי.
למידע נוסף והצטרפות לקורס בקרו בדף קורס Front End באתר.
 

השאלה: הסבירו את ההבדל בין אימות מידע בצד הלקוח לאימות מידע בצד השרת, והציעו מימוש בקוד צד הלקוח המאמת מידע בטופס.

1. אימות מידע במערכות ווב

כל מערכת ווב שאתם בונים מורכבת משני חלקים לפחות: צד השרת וצד הלקוח. תפיסת הפיתוח הרווחת היום מציבה את שני חלקים אלו כשתי מערכות נפרדות, כל אחת עם תפקידים ייחודיים עבורה וממשק עבודה מוגדר בין שתיהן. חלוקה זו מייצרת את הבסיס לשני מנגנוני אימות קלט אותם נצטרך לפתח, אחד לצד השרת והשני לצד הלקוח. על צד השרת מוטלת האחריות לוודא שלא נכנס קלט לא תקני למערכת. אימות בצד השרת לא חייב לדווח מה בדיוק תקין או לא תקין בקלט, ולרוב אפשר להסתפק בהודעת שגיאה אחידה או אף הסתמכות על HTTP Status Code (ערך 400 מייצג בקשה לא תקינה).
תפקיד האימות בצד הלקוח הוא שונה: לקוד בצד הלקוח אין ערך מבחינת אבטחת מידע או תקינות המידע הנשמר במערכת. מטרת האימות בצד הלקוח היא שיפור חווית המשתמש באמצעות דיווח על קלט שצפוי להכשל לפני שבכלל שולחים אותו לשרת. 

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

2. אימות מידע בטופס באמצעות JavaScript באופן נקודתי

מבחינת קוד, אימות מידע בטופס באמצעות JavaScript דורש הוספה של Event Handler לאירוע Submit של הטופס, אירוע שקורה בכל פעם שמשתמש מנסה לשלוח את המידע בטופס לשרת. קוד הטיפול באירוע יעבור על השדות השונים בטפסים ויוודא שהשדות אכן מכילים ערכים המתאימים ללוגיקה העסקית שלנו, לדוגמא ששדה אימייל אכן מכיל כתובת מייל ואיננו ריק, ושדה סיסמא מכיל סיסמא באורך 6 תווים ומעלה.
הפתרון משתמש בפונקציות ה JavaScript הבאות:

  1. תחילה נשלוף את האלמנטים הרלוונטים לתוכנית באמצעות querySelector.
  2. לאחר מכן נוסיף קוד טיפול לאירוע submit באמצעות addEventListener.
  3. בקוד האירוע נשתמש בפונקציה preventDefault כדי למנוע שליחת הטופס במידה והמידע לא תקין.

להלן קוד הפתרון והתוצאה:

 

See the Pen jEXayb by ynonp (@ynonp) on CodePen.

3. שיפור הפתרון באמצעות CSS Classes

בעייה מרכזית בפתרון זה היא הדיווח על שגיאות. קוד JavaScript איננו המקום הטוב ביותר להגדיר כיצד לדווח למשתמש על שגיאה (במקרה שלנו alert), וגם לא מה לכתוב בתוכן השגיאה. הקפצת הודעות שגיאה מסוג זה רק פוגעת בחווית המשתמש במקום לשפר אותה, מה שהופך את האימות בצד הלקוח לחסר ערך.
שיפור לפתרון זה יהיה הוספת קוד HTML לצורך דיווח על שגיאה, או במילים אחרות בניית הטופס בשני מצבים — מצב תקין ומצב שגיאה. מה שמפריד בין שני המצבים הינו CSS Class על כל אחד מהפקדים בטופס. כך במידה ופקד מסוים נכשל באימות אנו נעביר אותו למצב שגיאה באמצעות הוספת ה class.

אנו נשתמש בפונקציות הבאות כדי לעדכן את ה CSS:

  1. classList.add לצורך הוספת class לאלמנט.
  2. classList.remove לצורך מחיקת class מאלמנט.

להלן קוד הפתרון המשופר:

 

See the Pen YPdEZJ by ynonp (@ynonp) on CodePen.

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

4. מעבר לקוד פתרון כללי

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

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

function PasswordValidation(el) {
var self = this;
self.init(el);

self._isValid = function() {
    return this.input.value.length >= 6;
  };
}
PasswordValidation.prototype = ValidationPrototype;

שימו לב שכל מנגנון אימות משתמש באבטיפוס (Prototype) אחיד. תפקיד ה Prototype הוא לבצע את הפעולות המשותפות לכל מנגנוני האימות, למשל להעביר את הפקד למצב שגיאה אם האימות נכשל, או לאתחל את השדה this.input עבור אובייקט האימות. כל הקוד המשותף למנגנוני האימות השונים נכתב ב Prototype.

לצורך העברת המידע מ HTML ל JavaScript השתמשתי במאפייני Data. הגדרת מאפיין המתחיל במילה data על אלמנט ב HTML מאפשרת גישה קלה לתוכן המאפיין מתוך JavaScript באמצעות אובייקט dataset. כך לדוגמא עבור האלמנט הבא ב HTML:

<div id="mydiv" data-text="hello"></div>

ניתן לגשת לתוכן מאפיין ה data באמצעות קוד ה JavaScript הבא:

// true
document.querySelector('#mydiv').dataset.text === "hello";

להלן קוד הפתרון המלא:

 

See the Pen QwzOQN by ynonp (@ynonp) on CodePen.

5. ואיך אפשר לשכוח את HTML5

הכיוון אליו הולכים בפיתוח ווב כיום הוא העברת כמה שיותר פעולות גנריות אל הדפדפן, ובעצם צמצום השימוש ב JavaScript לדברים שכולם צריכים. הדוגמא הקלאסים ל״דברים שכולם צריכים״ היא כמובן אימות טפסים, ולכן HTML5 הביא אתו אוסף יכולות חדשות לאימות טפסים באמצעות הדפדפן וללא שימוש ב JavaScript כלל. 

אתם וודאי מכירים את האפשרות לציין עבור פקד input שסוגו הוא email, וכך לקבל אימות מהדפדפן שמשתמש אכן מקליד כתובת מייל תקנית בפקד. אימות זה הוא רק ההתחלה, וניתן כיום להגדיר על שדות קלט שהם נדרשים או אופציונאליים, מה האורך הנדרש בשדה ואף ביטוי רגולארי לאימות הקלט. כך למשל ייראה קוד HTML עבור הטופס שלנו שיעביר את משימת אימות הקלטים לדפדפן ומבצע את אותה עבודת אימות ללא קוד JavaScript כלל:

 

See the Pen gbZXKV by ynonp (@ynonp) on CodePen.

6. קישורים לקריאה נוספת

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

  1. Validity: פתרון אימות מבוסס jQuery:
    http://validity.thatscaptaintoyou.com/
  2. Validetta: פתרון נוסף מבוסס jQuery:
    http://lab.hasanaydogdu.com/validetta/
  3. מדריך מפורט על אימות טפסים באמצעות HTML5 וללא קוד JavaScript:
    https://developers.google.com/web/fundamentals/input/form/provide-real-time-validation?hl=en

 

7. הבהרה בנוגע ל XSS

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

לגבי XSS המצב מורכב משום שהבעייה היא הפוכה - במקום שהקלט הרע יגיע מהלקוח לשרת, הוא מגיע מהשרת ללקוח. לקוד צד-לקוח אין אפשרות לדעת האם אלמנט מסוים ב DOM אמור לכלול קוד להרצה או לא. תחשוב על מצב שאתה מקבל מהשרת בתגובה לבקשת Ajax את קטע ה HTML הבא המייצג תמונה:

<img src="..." onload="..." />

אין דרך לדעת בצד הלקוח האם הסקריפט שרשום בתוך מאפיין onload אכן צריך היה להיות שם או שמדובר במתקפת XSS.

מסיבה זו ניקוי הקלט מתווי HTML זדוניים מתבצע בצד השרת לפני שליחת ה HTML ללקוח. ב PHP למשל יש את הספריה הזו שתפקידה לנקות קטעי HTML שהתקבלו מלקוחות:
http://htmlpurifier.org/

דרך התגוננות שניה וחשובה אל מול XSS שעדיין לא נמצאת בשימוש נרחב הינה CSP. בשיטה זו אנו מבקשים מהדפדפן להריץ רק קבצי JS ממקורות ידועים ולא להריץ קוד JS המוטמע בתוך HTML. אפשר לקרוא עוד על CSP כאן:
http://www.html5rocks.com/en/tutorials/security/content-security-policy/

מה שכן, ספריות MVC וספריות Templates בצד הלקוח (Handlebars היא הראשונה שעולה לי לראש, אך יש עוד) מיישמות מנגנון של ניקוי קוד HTML מתבניות באופן אוטומטי. ספריות אלו אכן מדגימות את התפקיד של קוד צד-לקוח בהגנה על המשתמש מפני מתקפות XSS (בנוסף לניקוי שאמור להתבצע בצד השרת).

ותודה ליובל סיני שהסב את תשומת ליבי לנושא ה XSS.

*** רוצה ללמוד עוד על פיתוח צד-לקוח מודרני באמצעות JavaScript ו HTML5 ? קורס HTML5 Web Development שלנו מכסה את כל החומר הנדרש כיום בתעשייה לצורך פיתוח בטכנולוגיות אלו.