אימות באמצעות Apple ב-Android

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

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

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

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

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

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

באתר Apple Developer, מבצעים את הפעולות הבאות:

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

    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler

    אפשר לראות את מזהה פרויקט Firebase בדף ההגדרות של מסוף Firebase.

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

  2. יוצרים מפתח פרטי של 'כניסה באמצעות אפל'. בקטע הבא תצטרכו את המפתח הפרטי החדש ואת מזהה המפתח.
  3. אם אתם משתמשים בתכונות של Firebase Authentication ששולחות אימיילים למשתמשים, כולל כניסה באמצעות קישור באימייל, אימות כתובת אימייל, ביטול שינוי בחשבון ועוד, אתם צריכים להגדיר את שירות העברת האימייל הפרטי של אפל ולרשום את noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (או את הדומיין של תבנית האימייל המותאמת אישית שלכם) כדי שאפל תוכל להעביר אימיילים שנשלחים על ידי Firebase Authentication לכתובות אימייל אנונימיות של אפל.

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

  1. איך מוסיפים את Firebase לפרויקט Android חשוב לרשום את חתימת ה-SHA-1 של האפליקציה כשמגדירים את האפליקציה במסוף Firebase.
  2. במסוף Firebase, פותחים את הקטע אימות. בכרטיסייה שיטת הכניסה, מפעילים את הספק Apple. מציינים את מזהה השירות שיצרתם בקטע הקודם. בנוסף, בקטע OAuth code flow configuration (הגדרת תהליך קוד OAuth), מציינים את מזהה הצוות שלכם ב-Apple, את המפתח הפרטי ואת מזהה המפתח שיצרתם בקטע הקודם.

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

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

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

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

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

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

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

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

  1. יוצרים מופע של OAuthProvider באמצעות ה-Builder שלו עם מזהה הספק apple.com:

    Kotlin

    val provider = OAuthProvider.newBuilder("apple.com")
    

    Java

    OAuthProvider.Builder provider = OAuthProvider.newBuilder("apple.com");
    
  2. אופציונלי: מציינים היקפי הרשאות נוספים של OAuth 2.0 מעבר לברירת המחדל שרוצים לבקש מספק האימות.

    Kotlin

    provider.setScopes(arrayOf("email", "name"))
    

    Java

    List<String> scopes =
        new ArrayList<String>() {
          {
            add("email");
            add("name");
          }
        };
    provider.setScopes(scopes);
    

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

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

    Kotlin

    // Localize the Apple authentication screen in French.
    provider.addCustomParameter("locale", "fr")
    

    Java

    // Localize the Apple authentication screen in French.
    provider.addCustomParameter("locale", "fr");
    
  4. מתבצע אימות ב-Firebase באמצעות אובייקט ספק OAuth. שימו לב שבניגוד לפעולות אחרות של FirebaseAuth, הפעולה הזו תשתלט על ממשק המשתמש על ידי פתיחת כרטיסייה מותאמת אישית ב-Chrome. לכן, אל תפנו לפעילות שלכם ב-OnSuccessListener וב-OnFailureListener שאתם מצרפים, כי הם ינותקו מיד כשהפעולה תתחיל את ממשק המשתמש.

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

    כדי לבדוק אם יש תוצאה בהמתנה, מתקשרים אל getPendingAuthResult():

    Kotlin

    val pending = auth.pendingAuthResult
    if (pending != null) {
        pending.addOnSuccessListener { authResult ->
            Log.d(TAG, "checkPending:onSuccess:$authResult")
            // Get the user profile with authResult.getUser() and
            // authResult.getAdditionalUserInfo(), and the ID
            // token from Apple with authResult.getCredential().
        }.addOnFailureListener { e ->
            Log.w(TAG, "checkPending:onFailure", e)
        }
    } else {
        Log.d(TAG, "pending: null")
    }
    

    Java

    mAuth = FirebaseAuth.getInstance();
    Task<AuthResult> pending = mAuth.getPendingAuthResult();
    if (pending != null) {
        pending.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
            @Override
            public void onSuccess(AuthResult authResult) {
                Log.d(TAG, "checkPending:onSuccess:" + authResult);
                // Get the user profile with authResult.getUser() and
                // authResult.getAdditionalUserInfo(), and the ID
                // token from Apple with authResult.getCredential().
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.w(TAG, "checkPending:onFailure", e);
            }
        });
    } else {
        Log.d(TAG, "pending: null");
    }
    

    אם אין תוצאה בהמתנה, מתחילים את תהליך הכניסה על ידי קריאה ל-startActivityForSignInWithProvider():

    Kotlin

    auth.startActivityForSignInWithProvider(this, provider.build())
            .addOnSuccessListener { authResult ->
                // Sign-in successful!
                Log.d(TAG, "activitySignIn:onSuccess:${authResult.user}")
                val user = authResult.user
                // ...
            }
            .addOnFailureListener { e ->
                Log.w(TAG, "activitySignIn:onFailure", e)
            }
    

    Java

    mAuth.startActivityForSignInWithProvider(this, provider.build())
            .addOnSuccessListener(
                    new OnSuccessListener<AuthResult>() {
                        @Override
                        public void onSuccess(AuthResult authResult) {
                            // Sign-in successful!
                            Log.d(TAG, "activitySignIn:onSuccess:" + authResult.getUser());
                            FirebaseUser user = authResult.getUser();
                            // ...
                        }
                    })
            .addOnFailureListener(
                    new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            Log.w(TAG, "activitySignIn:onFailure", e);
                        }
                    });
    

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

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

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

אימות מחדש וקישור חשבונות

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

Kotlin

// The user is already signed-in.
val firebaseUser = auth.getCurrentUser()

firebaseUser
    .startActivityForReauthenticateWithProvider(/* activity= */ this, provider.build())
    .addOnSuccessListener( authResult -> {
        // User is re-authenticated with fresh tokens and
        // should be able to perform sensitive operations
        // like account deletion and email or password
        // update.
    })
    .addOnFailureListener( e -> {
        // Handle failure.
    })

Java

// The user is already signed-in.
FirebaseUser firebaseUser = mAuth.getCurrentUser();

firebaseUser
    .startActivityForReauthenticateWithProvider(/* activity= */ this, provider.build())
    .addOnSuccessListener(
        new OnSuccessListener<AuthResult>() {
          @Override
          public void onSuccess(AuthResult authResult) {
            // User is re-authenticated with fresh tokens and
            // should be able to perform sensitive operations
            // like account deletion and email or password
            // update.
          }
        })
    .addOnFailureListener(
        new OnFailureListener() {
          @Override
          public void onFailure(@NonNull Exception e) {
            // Handle failure.
          }
        });

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

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

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

Kotlin

// Initialize a Facebook credential with a Facebook access token.
val credential = FacebookAuthProvider.getCredential(token.getToken())

// Assuming the current user is an Apple user linking a Facebook provider.
mAuth.getCurrentUser().linkWithCredential(credential)
    .addOnCompleteListener(this, task -> {
        if (task.isSuccessful()) {
          // Facebook credential is linked to the current Apple user.
          // The user can now sign in to the same account
          // with either Apple or Facebook.
        }
      });

Java

// Initialize a Facebook credential with a Facebook access token.
AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken());

// Assuming the current user is an Apple user linking a Facebook provider.
mAuth.getCurrentUser().linkWithCredential(credential)
    .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
      @Override
      public void onComplete(@NonNull Task<AuthResult> task) {
        if (task.isSuccessful()) {
          // Facebook credential is linked to the current Apple user.
          // The user can now sign in to the same account
          // with either Apple or Facebook.
        }
      }
    });

למתקדמים: ניהול תהליך הכניסה באופן ידני

אפשר גם לבצע אימות ב-Firebase באמצעות חשבון אפל. כדי לעשות זאת, צריך לטפל בתהליך הכניסה באמצעות Apple Sign-In JS SDK, לבנות ידנית את תהליך OAuth או להשתמש בספריית OAuth כמו AppAuth.

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

    אפשר ליצור ערך חד-פעמי מאובטח מבחינה קריפטוגרפית ב-Android באמצעות SecureRandom, כמו בדוגמה הבאה:

    Kotlin

    private fun generateNonce(length: Int): String {
        val generator = SecureRandom()
    
        val charsetDecoder = StandardCharsets.US_ASCII.newDecoder()
        charsetDecoder.onUnmappableCharacter(CodingErrorAction.IGNORE)
        charsetDecoder.onMalformedInput(CodingErrorAction.IGNORE)
    
        val bytes = ByteArray(length)
        val inBuffer = ByteBuffer.wrap(bytes)
        val outBuffer = CharBuffer.allocate(length)
        while (outBuffer.hasRemaining()) {
            generator.nextBytes(bytes)
            inBuffer.rewind()
            charsetDecoder.reset()
            charsetDecoder.decode(inBuffer, outBuffer, false)
        }
        outBuffer.flip()
        return outBuffer.toString()
    }
    

    Java

    private String generateNonce(int length) {
        SecureRandom generator = new SecureRandom();
    
        CharsetDecoder charsetDecoder = StandardCharsets.US_ASCII.newDecoder();
        charsetDecoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
        charsetDecoder.onMalformedInput(CodingErrorAction.IGNORE);
    
        byte[] bytes = new byte[length];
        ByteBuffer inBuffer = ByteBuffer.wrap(bytes);
        CharBuffer outBuffer = CharBuffer.allocate(length);
        while (outBuffer.hasRemaining()) {
            generator.nextBytes(bytes);
            inBuffer.rewind();
            charsetDecoder.reset();
            charsetDecoder.decode(inBuffer, outBuffer, false);
        }
        outBuffer.flip();
        return outBuffer.toString();
    }
    

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

    Kotlin

    private fun sha256(s: String): String {
        val md = MessageDigest.getInstance("SHA-256")
        val digest = md.digest(s.toByteArray())
        val hash = StringBuilder()
        for (c in digest) {
            hash.append(String.format("%02x", c))
        }
        return hash.toString()
    }
    

    Java

    private String sha256(String s) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] digest = md.digest(s.getBytes());
        StringBuilder hash = new StringBuilder();
        for (byte c: digest) {
            hash.append(String.format("%02x", c));
        }
        return hash.toString();
    }
    

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

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

  3. אחרי שמקבלים את התשובה של Apple, מקבלים את טוקן ה-ID מהתשובה ומשתמשים בו ובערך ה-nonce שלא עבר גיבוב כדי ליצור AuthCredential:

    Kotlin

    val credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build()
    

    Java

    AuthCredential credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build();
    
  4. אימות באמצעות Firebase באמצעות פרטי הכניסה של Firebase:

    Kotlin

    auth.signInWithCredential(credential)
          .addOnCompleteListener(this) { task ->
              if (task.isSuccessful) {
                // User successfully signed in with Apple ID token.
                // ...
              }
          }
    

    Java

    mAuth.signInWithCredential(credential)
        .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
          @Override
          public void onComplete(@NonNull Task<AuthResult> task) {
            if (task.isSuccessful()) {
              // User successfully signed in with Apple ID token.
              // ...
            }
          }
        });
    

אם הקריאה אל signInWithCredential מצליחה, אפשר להשתמש בשיטה getCurrentUser כדי לקבל את נתוני החשבון של המשתמש.

ביטול טוקן

‫Apple דורשת שאפליקציות שתומכות ביצירת חשבון יאפשרו למשתמשים להתחיל את תהליך מחיקת החשבון שלהם בתוך האפליקציה, כפי שמתואר בהנחיות לבדיקת אפליקציות ב-App Store.

בנוסף, באפליקציות שתומכות בכניסה באמצעות Apple, צריך להשתמש ב-REST API של 'כניסה באמצעות Apple' כדי לבטל את האסימונים של המשתמשים.

כדי לעמוד בדרישה הזו, צריך לבצע את השלבים הבאים:

  1. משתמשים בשיטה startActivityForSignInWithProvider() כדי להיכנס באמצעות Apple ולקבל AuthResult.

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

    Kotlin

    val oauthCredential: OAuthCredential =  authResult.credential
    val accessToken = oauthCredential.accessToken
    

    Java

    OAuthCredential oauthCredential = (OAuthCredential) authResult.getCredential();
    String accessToken = oauthCredential.getAccessToken();
    
  3. מבטלים את הטוקן באמצעות revokeAccessToken API.

    Kotlin

    mAuth.revokeAccessToken(accessToken)
      .addOnCompleteListener(this) { task ->
        if (task.isSuccessful) {
          // Access token successfully revoked
          // for the user ...
        }
    }
    

    Java

    mAuth.revokeAccessToken(accessToken)
        .addOnCompleteListener(this, new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
              if (task.isSuccessful()) {
                // Access token successfully revoked
                // for the user ...
              }
            }
      });
    
  1. לבסוף, מוחקים את חשבון המשתמש (ואת כל הנתונים שמשויכים אליו).

    השלבים הבאים

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

    • באפליקציות שלכם, תוכלו לקבל את פרטי הפרופיל הבסיסיים של המשתמש מאובייקט FirebaseUser. ניהול משתמשים

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

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

    כדי להוציא משתמש מהחשבון, מתקשרים אל signOut:

    Kotlin

    Firebase.auth.signOut()

    Java

    FirebaseAuth.getInstance().signOut();