אימות באמצעות Apple ו-C++

אתם יכולים לאפשר למשתמשים שלכם לבצע אימות באמצעות Firebase באמצעות Apple ID שלהם, על ידי שימוש ב-Firebase SDK כדי לבצע את תהליך הכניסה של OAuth 2.0 מקצה לקצה.

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

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

הצטרפות לתוכנית המפתחים של אפל

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

הגדרת כניסה באמצעות Apple

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

הפעלת Apple כספק כניסה

  1. במסוף Firebase, עוברים אל Security (אבטחה) > Authentication (אימות).
  2. בכרטיסייה שיטת כניסה, מפעילים את ספק הכניסה אפל.
  3. מגדירים את ספק הכניסה באמצעות Apple:
    • Apple: אם אתם פורסים את האפליקציה רק בפלטפורמות של Apple, אתם יכולים להשאיר את השדות Service ID, ‏ Apple Team ID, ‏ private key ו-key ID ריקים.
    • Android: כדי לתמוך במכשירי Android, צריך לבצע את הפעולות הבאות:
      1. איך מוסיפים את Firebase לפרויקט Android
      2. אם עדיין לא עשיתם זאת, מציינים את טביעת האצבע מסוג SHA-1 של האפליקציה.
        1. במסוף Firebase, עוברים אל הגדרות > הכרטיסייה כללי.
        2. גוללים למטה לכרטיס האפליקציות שלך, בוחרים את האפליקציה ל-Android ומוסיפים את טביעת האצבע של אישור ה-SHA בשדה טביעות אצבע של אישור SHA.

        במאמר אימות הלקוח מוסבר איך מקבלים את טביעת האצבע של ה-SHA של האפליקציה.

      3. מגדירים את ספק הכניסה באמצעות חשבון Apple:
        1. במסוף Firebase, עוברים אל Security (אבטחה) > Authentication (אימות).
        2. בכרטיסייה שיטת הכניסה, לוחצים על ספק הכניסה Apple.
        3. מציינים את מזהה השירות שיצרתם בקטע הקודם. בנוסף, בקטע ההגדרות של תהליך קבלת הרשאת OAuth, מציינים את מזהה הצוות שלכם ב-Apple, את המפתח הפרטי ואת מזהה המפתח שיצרתם בקטע הקודם.

עמידה בדרישות של Apple לגבי נתונים שעברו אנונימיזציה

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

הדבר כולל קבלת הסכמה מהמשתמש הנדרשת לפני שמשייכים פרטים אישיים מזהים ישירות למזהה Apple אנונימי. כשמשתמשים באימות ב-Firebase, הפעולות האלה עשויות לכלול את הפעולות הבאות:

  • לקשר כתובת אימייל ל-Apple ID שעבר אנונימיזציה או להיפך.
  • קישור מספר טלפון ל-Apple ID אנונימי או להיפך
  • לקשר פרטי כניסה לרשתות חברתיות שלא עברו אנונימיזציה (פייסבוק, Google וכו') למזהה Apple שעבר אנונימיזציה או להיפך.

הרשימה שלמעלה היא חלקית. כדי לוודא שהאפליקציה עומדת בדרישות של Apple, אפשר לעיין בהסכם הרישיון של Apple Developer Program בקטע Membership בחשבון הפיתוח.

גישה לכיתה firebase::auth::Auth

המחלקות Auth הן שער לכל הקריאות ל-API.
  1. מוסיפים את קובצי הכותרות Auth ו-App:
    #include "firebase/app.h"
    #include "firebase/auth.h"
  2. יוצרים מחלקה firebase::App בקוד האתחול.
    #if defined(__ANDROID__)
      firebase::App* app =
          firebase::App::Create(firebase::AppOptions(), my_jni_env, my_activity);
    #else
      firebase::App* app = firebase::App::Create(firebase::AppOptions());
    #endif  // defined(__ANDROID__)
  3. קבלת הכיתה firebase::auth::Auth ל-firebase::App. יש מיפוי של אחד לאחד בין App לבין Auth.
    firebase::auth::Auth* auth = firebase::auth::Auth::GetAuth(app);

טיפול בתהליך הכניסה באמצעות Firebase SDK

תהליך הכניסה באמצעות Apple שונה בפלטפורמות של Apple ובפלטפורמות של Android.

בפלטפורמות של Apple

מאמתים את המשתמשים באמצעות Firebase דרך Apple Sign In Objective-C SDK שמופעל מקוד C++.

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

      - (NSString *)randomNonce:(NSInteger)length {
        NSAssert(length > 0, @"Expected nonce to have positive length");
        NSString *characterSet = @"0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._";
        NSMutableString *result = [NSMutableString string];
        NSInteger remainingLength = length;
    
        while (remainingLength > 0) {
          NSMutableArray *randoms = [NSMutableArray arrayWithCapacity:16];
          for (NSInteger i = 0; i < 16; i++) {
            uint8_t random = 0;
            int errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random);
            NSAssert(errorCode == errSecSuccess, @"Unable to generate nonce: OSStatus %i", errorCode);
    
            [randoms addObject:@(random)];
          }
    
          for (NSNumber *random in randoms) {
            if (remainingLength == 0) {
              break;
            }
    
            if (random.unsignedIntValue < characterSet.length) {
              unichar character = [characterSet characterAtIndex:random.unsignedIntValue];
              [result appendFormat:@"%C", character];
              remainingLength--;
            }
          }
        }
      }
    
    

    תשלחו את גיבוב SHA256 של הערך החד-פעמי עם בקשת הכניסה, ו-Apple תעביר אותו ללא שינוי בתגובה. מערכת Firebase מאמתת את התגובה על ידי גיבוב של ה-nonce המקורי והשוואה שלו לערך שמועבר על ידי Apple.

  2. מתחילים את תהליך הכניסה של אפל, כולל הגיבוב SHA256 של ה-nonce ומחלקת הנציגים שתטפל בתגובה של אפל (ראו את השלב הבא):

      - (void)startSignInWithAppleFlow {
        NSString *nonce = [self randomNonce:32];
        self.currentNonce = nonce;
        ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
        ASAuthorizationAppleIDRequest *request = [appleIDProvider createRequest];
        request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
        request.nonce = [self stringBySha256HashingString:nonce];
    
        ASAuthorizationController *authorizationController =
            [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
        authorizationController.delegate = self;
        authorizationController.presentationContextProvider = self;
        [authorizationController performRequests];
      }
    
      - (NSString *)stringBySha256HashingString:(NSString *)input {
        const char *string = [input UTF8String];
        unsigned char result[CC_SHA256_DIGEST_LENGTH];
        CC_SHA256(string, (CC_LONG)strlen(string), result);
    
        NSMutableString *hashed = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
        for (NSInteger i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
          [hashed appendFormat:@"%02x", result[i]];
        }
        return hashed;
      }
    
  3. צריך לטפל בתגובה של אפל בהטמעה של ASAuthorizationControllerDelegate. אם הכניסה בוצעה בהצלחה, צריך להשתמש בטוקן המזהה מהתגובה של אפל עם ה-nonce הלא-מגובב כדי לבצע אימות ב-Firebase:

      - (void)authorizationController:(ASAuthorizationController *)controller
         didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) {
        if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
          ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;
          NSString *rawNonce = self.currentNonce;
          NSAssert(rawNonce != nil, @"Invalid state: A login callback was received, but no login request was sent.");
    
          if (appleIDCredential.identityToken == nil) {
            NSLog(@"Unable to fetch identity token.");
            return;
          }
    
          NSString *idToken = [[NSString alloc] initWithData:appleIDCredential.identityToken
                                                    encoding:NSUTF8StringEncoding];
          if (idToken == nil) {
            NSLog(@"Unable to serialize id token from data: %@", appleIDCredential.identityToken);
          }
        }
    
  4. משתמשים במחרוזת האסימון ובערך ה-nonce המקורי כדי ליצור Firebase Credential ולהיכנס ל-Firebase.

    firebase::auth::OAuthProvider::GetCredential(
            /*provider_id=*/"apple.com", token, nonce,
            /*access_token=*/nullptr);
    
    firebase::Future<firebase::auth::AuthResult> result =
        auth->SignInAndRetrieveDataWithCredential(credential);
    
  5. אפשר להשתמש באותו דפוס עם Reauthenticate כדי לאחזר פרטי כניסה עדכניים לפעולות רגישות שדורשות כניסה עדכנית.

    firebase::Future<firebase::auth::AuthResult> result =
        user->Reauthenticate(credential);
    
  6. אפשר להשתמש באותו דפוס כדי לקשר חשבון לכניסה באמצעות חשבון אפל. עם זאת, יכול להיות שתיתקלו בשגיאה אם חשבון Firebase קיים כבר קושר לחשבון אפל שאליו אתם מנסים לקשר. במקרה כזה, בעתיד יוחזר סטטוס kAuthErrorCredentialAlreadyInUse, והשדה AuthResult עשוי להכיל ערך credential תקין. אפשר להשתמש בהרשאה הזו כדי להיכנס לחשבון שמקושר ל-Apple דרך SignInAndRetrieveDataWithCredential בלי ליצור עוד אסימון וערך חד-פעמי של 'כניסה באמצעות Apple'.

    firebase::Future<firebase::auth::AuthResult> link_result =
        auth->current_user().LinkWithCredential(credential);
    
    // To keep example simple, wait on the current thread until call completes.
    while (link_result.status() == firebase::kFutureStatusPending) {
      Wait(100);
    }
    
    // Determine the result of the link attempt
    if (link_result.error() == firebase::auth::kAuthErrorNone) {
      // user linked correctly.
    } else if (link_result.error() ==
                   firebase::auth::kAuthErrorCredentialAlreadyInUse &&
               link_result.result()
                   ->additional_user_info.updated_credential.is_valid()) {
      // Sign In with the new credential
      firebase::Future<firebase::auth::AuthResult> result =
          auth->SignInAndRetrieveDataWithCredential(
              link_result.result()->additional_user_info.updated_credential);
    } else {
      // Another link error occurred.
    }

ב-Android

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

כדי לטפל בתהליך הכניסה באמצעות Firebase SDK, פועלים לפי השלבים הבאים:

  1. יוצרים מופע של FederatedOAuthProviderData שמוגדר עם מזהה הספק שמתאים ל-Apple.

    firebase::auth::FederatedOAuthProviderData provider_data("apple.com");
    
  2. אופציונלי: מציינים היקפי הרשאות נוספים של OAuth 2.0 מעבר להיקף ברירת המחדל שרוצים לבקש מספק האימות.

    provider_data.scopes.push_back("email");
    provider_data.scopes.push_back("name");
    
  3. אופציונלי: אם רוצים להציג את מסך הכניסה של אפל בשפה שאינה אנגלית, מגדירים את הפרמטר locale. במסמכים בנושא 'כניסה באמצעות Apple' מפורטים הלוקאלים הנתמכים.

    // Localize to French.
    provider_data.custom_parameters["language"] = "fr";
    ```
    
  4. אחרי שמגדירים את נתוני הספק, משתמשים בהם כדי ליצור FederatedOAuthProvider.

    // Construct a FederatedOAuthProvider for use in Auth methods.
    firebase::auth::FederatedOAuthProvider provider(provider_data);
    
  5. מתבצע אימות באמצעות Firebase באמצעות אובייקט ספק האימות. שימו לב: בניגוד לפעולות אחרות של FirebaseAuth, הפעולה הזו תשתלט על ממשק המשתמש על ידי הצגת תצוגת אינטרנט שבה המשתמש יכול להזין את פרטי הכניסה שלו.

    כדי להתחיל את תהליך הכניסה, קוראים לפונקציה signInWithProvider:

    firebase::Future<firebase::auth::AuthResult> result =
      auth->SignInWithProvider(provider_data);
    

    יכול להיות שהבקשה תמתין או תירשם כקריאה חוזרת ב-Future.

  6. אפשר להשתמש באותו דפוס עם ReauthenticateWithProvider כדי לאחזר פרטי כניסה עדכניים לפעולות רגישות שדורשות כניסה עדכנית.

    firebase::Future<firebase::auth::AuthResult> result =
      user.ReauthenticateWithProvider(provider_data);
    

    יכול להיות שהאפליקציה תמתין או תרשום קריאה חוזרת ב-Future.

  7. בנוסף, אפשר להשתמש ב-LinkWithCredential() כדי לקשר ספקי זהויות שונים לחשבונות קיימים.

    חשוב לזכור שחברת אפל דורשת לקבל מהמשתמשים הסכמה מפורשת לפני שמקשרים את החשבונות שלהם ב-Apple לנתונים אחרים.

    לדוגמה, כדי לקשר חשבון פייסבוק לחשבון Firebase הנוכחי, משתמשים באסימון הגישה שקיבלתם מהתחברות המשתמש לפייסבוק:

    // Initialize a Facebook credential with a Facebook access token.
    AuthCredential credential =
        firebase::auth::FacebookAuthProvider.getCredential(token);
    
    // Assuming the current user is an Apple user linking a Facebook provider.
    firebase::Future<firebase::auth::AuthResult> result =
        auth.current_user().LinkWithCredential(credential);
    

כניסה באמצעות Apple Notes

בניגוד לספקים אחרים שנתמכים על ידי Firebase Auth, ‏ Apple לא מספקת כתובת URL של תמונה.

בנוסף, אם המשתמש בוחר לא לשתף את כתובת האימייל שלו עם האפליקציה, אפל מספקת כתובת אימייל ייחודית למשתמש (בפורמט xyz@privaterelay.appleid.com) ומשתפת אותה עם האפליקציה שלכם. אם הגדרתם את שירות העברת האימייל הפרטי, אפל מעבירה אימיילים שנשלחים לכתובת האנונימית לכתובת האימייל האמיתית של המשתמש.

‫Apple משתפת מידע על משתמשים, כמו השם לתצוגה, רק עם אפליקציות שמשתמש נכנס אליהן בפעם הראשונה. בדרך כלל, Firebase שומר את שם התצוגה בפעם הראשונה שמשתמש נכנס לחשבון באמצעות Apple, ואפשר לקבל אותו באמצעות current_user().display_name(). עם זאת, אם השתמשתם בעבר ב-Apple כדי לאפשר למשתמש להיכנס לאפליקציה בלי להשתמש ב-Firebase, ‏ Apple לא תספק ל-Firebase את השם המוצג של המשתמש.

השלבים הבאים

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

באפליקציות, אפשר לקבל את פרטי הפרופיל הבסיסיים של המשתמש מאובייקט firebase::auth::User. איך מנהלים משתמשים

בכללי האבטחה של מסד נתונים בזמן אמת ב-Firebase ו-Cloud Storage, אפשר לקבל את מזהה המשתמש הייחודי של המשתמש המחובר מהמשתנה auth, ולהשתמש בו כדי לקבוע לאילו נתונים המשתמש יכול לגשת.