הוספת אימות רב-גורמי לאפליקציה ב-Flutter

אם שדרגתם לאימות ב-Firebase באמצעות Identity Platform, תוכלו להוסיף לאפליקציה שלכם ב-Flutter אימות רב-שלבי באמצעות SMS.

אימות רב-שלבי (MFA) מגביר את האבטחה של האפליקציה. לרוב, תוקפים מצליחים לפרוץ לסיסמאות ולחשבונות ברשתות החברתיות, אבל קשה יותר להם ליירט הודעת SMS.

לפני שמתחילים

  1. מפעילים ספק אחד לפחות שתומך באימות רב-שלבי. כל הספקים תומכים באימות דו-שלבי, למעט אימות בטלפון, אימות אנונימי ו-Apple Game Center.

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

  3. Android: אם עדיין לא הגדרתם את הגיבוב SHA-256 של האפליקציה במסוף Firebase, עליכם לעשות זאת. במאמר אימות הלקוח מוסבר איך למצוא את הגיבוב SHA-256 של האפליקציה.

  4. iOS: ב-Xcode, מפעילים את ההתראות ב-push בפרויקט ומוודאים שמפתח האימות של APNs מוגדר ב-Firebase Cloud Messaging ‏ (FCM). בנוסף, צריך להפעיל את המצבים ברקע כדי לקבל התראות מרחוק. הסבר מפורט על השלב הזה זמין במסמכי התיעוד של אימות הטלפון ב-Firebase ל-iOS.

  5. אינטרנט: מוודאים שהוספתם את הדומיין של האפליקציות במסוף Firebase, בקטע דומיינים להפניה אוטומטית של OAuth.

הפעלת אימות רב-גורמי

  1. פותחים את הדף Authentication (אימות) > Sign-in method (שיטת כניסה) במסוף Firebase.

  2. בקטע Advanced, מפעילים את האפשרות SMS Multi-factor Authentication.

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

  3. אם עדיין לא הענקת הרשאה לדומיין של האפליקציה, צריך להוסיף אותו לרשימת ההיתרים בדף Authentication > Settings במסוף Firebase.

בחירת דפוס ההרשמה

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

  • להירשם לגורם האימות השני של המשתמש כחלק מהרישום. כדאי להשתמש בשיטה הזו אם האפליקציה שלכם דורשת אימות רב-שלבי לכל המשתמשים.

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

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

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

הוספת שלב שני

כדי להירשם לגורם אימות משני חדש למשתמש:

  1. מבצעים אימות מחדש של המשתמש.

  2. מבקשים מהמשתמש להזין את מספר הטלפון שלו.

  3. מקבלים סשן עם אימות רב-גורמי למשתמש:

    final multiFactorSession = await user.multiFactor.getSession();
    
  4. מאמתים את מספר הטלפון באמצעות סשן עם אימות רב-גורמי וקריאות חוזרות:

    await FirebaseAuth.instance.verifyPhoneNumber(
      multiFactorSession: multiFactorSession,
      phoneNumber: phoneNumber,
      verificationCompleted: (_) {},
      verificationFailed: (_) {},
      codeSent: (String verificationId, int? resendToken) async {
        // The SMS verification code has been sent to the provided phone number.
        // ...
      },
      codeAutoRetrievalTimeout: (_) {},
    );
    
  5. אחרי שליחת קוד ה-SMS, מבקשים מהמשתמש לאמת את הקוד:

    final credential = PhoneAuthProvider.credential(
      verificationId: verificationId,
      smsCode: smsCode,
    );
    
  6. משלימים את ההרשמה:

    await user.multiFactor.enroll(
      PhoneMultiFactorGenerator.getAssertion(
        credential,
      ),
    );
    

הקוד הבא מציג דוגמה מלאה להוספת גורם אימות שני:

  final session = await user.multiFactor.getSession();
  final auth = FirebaseAuth.instance;
  await auth.verifyPhoneNumber(
    multiFactorSession: session,
    phoneNumber: phoneController.text,
    verificationCompleted: (_) {},
    verificationFailed: (_) {},
    codeSent: (String verificationId, int? resendToken) async {
      // See `firebase_auth` example app for a method of retrieving user's sms code:
      // https://github.com/firebase/flutterfire/blob/main/packages/firebase_auth/firebase_auth/example/lib/auth.dart#L591
      final smsCode = await getSmsCodeFromUser(context);

      if (smsCode != null) {
        // Create a PhoneAuthCredential with the code
        final credential = PhoneAuthProvider.credential(
          verificationId: verificationId,
          smsCode: smsCode,
        );

        try {
          await user.multiFactor.enroll(
            PhoneMultiFactorGenerator.getAssertion(
              credential,
            ),
          );
        } on FirebaseAuthException catch (e) {
          print(e.message);
        }
      }
    },
    codeAutoRetrievalTimeout: (_) {},
  );

כל הכבוד! רשמתם בהצלחה גורם אימות שני למשתמש.

כניסה של משתמשים באמצעות גורם אימות שני

כדי לאפשר למשתמש להיכנס באמצעות אימות דו-שלבי באמצעות SMS:

  1. נכנסים עם המשתמש באמצעות הגורם הראשון, ואז תופסים את היוצא מן הכלל FirebaseAuthMultiFactorException. השגיאה הזו מכילה פותר (resolver) שבעזרתו אפשר לקבל את גורמי האימות הנוספים של המשתמש. הוא מכיל גם סשן בסיסי שמוכיח שהמשתמש ביצע אימות באמצעות הגורם הראשון.

    לדוגמה, אם הגורם הראשון של המשתמש היה כתובת אימייל וסיסמה:

    try {
      await _auth.signInWithEmailAndPassword(
          email: emailController.text,
          password: passwordController.text,
      );
      // User is not enrolled with a second factor and is successfully
      // signed in.
      // ...
    } on FirebaseAuthMultiFactorException catch (e) {
      // The user is a multi-factor user. Second factor challenge is required
      final resolver = e.resolver
      // ...
    }
    
  2. אם למשתמש יש כמה גורמים משניים רשומים, צריך לשאול אותו באיזה מהם להשתמש:

    final session = e.resolver.session;
    
    final hint = e.resolver.hints[selectedHint];
    
  3. שולחים הודעה לאימות לטלפון של המשתמש עם ההצעה והסשן של אימות הרבה-גורמים:

    await FirebaseAuth.instance.verifyPhoneNumber(
      multiFactorSession: session,
      multiFactorInfo: hint,
      verificationCompleted: (_) {},
      verificationFailed: (_) {},
      codeSent: (String verificationId, int? resendToken) async {
        // ...
      },
      codeAutoRetrievalTimeout: (_) {},
    );
    
  4. מתקשרים למספר resolver.resolveSignIn() כדי להשלים את האימות המשני:

    final smsCode = await getSmsCodeFromUser(context);
    if (smsCode != null) {
      // Create a PhoneAuthCredential with the code
      final credential = PhoneAuthProvider.credential(
        verificationId: verificationId,
        smsCode: smsCode,
      );
    
      try {
        await e.resolver.resolveSignIn(
          PhoneMultiFactorGenerator.getAssertion(credential)
        );
      } on FirebaseAuthException catch (e) {
        print(e.message);
      }
    }
    

הקוד הבא מציג דוגמה מלאה לכניסה של משתמש עם אימות רב-גורמי:

try {
  await _auth.signInWithEmailAndPassword(
    email: emailController.text,
    password: passwordController.text,
  );
} on FirebaseAuthMultiFactorException catch (e) {
  setState(() {
    error = '${e.message}';
  });
  final firstHint = e.resolver.hints.first;
  if (firstHint is! PhoneMultiFactorInfo) {
    return;
  }
  await FirebaseAuth.instance.verifyPhoneNumber(
    multiFactorSession: e.resolver.session,
    multiFactorInfo: firstHint,
    verificationCompleted: (_) {},
    verificationFailed: (_) {},
    codeSent: (String verificationId, int? resendToken) async {
      // See `firebase_auth` example app for a method of retrieving user's sms code:
      // https://github.com/firebase/flutterfire/blob/main/packages/firebase_auth/firebase_auth/example/lib/auth.dart#L591
      final smsCode = await getSmsCodeFromUser(context);

      if (smsCode != null) {
        // Create a PhoneAuthCredential with the code
        final credential = PhoneAuthProvider.credential(
          verificationId: verificationId,
          smsCode: smsCode,
        );

        try {
          await e.resolver.resolveSignIn(
            PhoneMultiFactorGenerator.getAssertion(
              credential,
            ),
          );
        } on FirebaseAuthException catch (e) {
          print(e.message);
        }
      }
    },
    codeAutoRetrievalTimeout: (_) {},
  );
} catch (e) {
  ...
}

כל הכבוד! התחברתם בהצלחה למשתמש באמצעות אימות רב-גורמי.

המאמרים הבאים