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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

טיפול בתהליך הכניסה באמצעות 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 מבקשת הרשאות גישה לשם ולכתובת האימייל. אם משנים את ההגדרה הזו לאפשרות Multiple accounts per email address, מערכת Firebase לא מבקשת היקפי גישה מ-Apple אלא אם מציינים אותם.

  3. אופציונלי: אם רוצים להציג את מסך הכניסה של Apple בשפה שאינה אנגלית, מגדירים את הפרמטר 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, Apple לא מספקת כתובת URL של תמונה.

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

    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 דורשת לקבל מהמשתמשים הסכמה מפורשת לפני שמקשרים את חשבונות Apple שלהם לנתונים אחרים.

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

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. לשם כך, מטפלים בתהליך הכניסה באמצעות Apple Sign-In JS SDK, יוצרים את תהליך OAuth באופן ידני או משתמשים בספריית OAuth כמו AppAuth.

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

    אפשר ליצור ב-Android קוד nonce מאובטח מבחינה קריפטוגרפית באמצעות 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();
    }
    

    לאחר מכן, מקבלים את הגיבוב (hash) של ה-nonce ב-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();
    }
    

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

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

  3. אחרי שמקבלים את התשובה מ-Apple, צריך לקבל את אסימון המזהה מהתשובה ולהשתמש בו וב-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 תצליח, תוכלו להשתמש ב-method‏ getCurrentUser כדי לקבל את נתוני החשבון של המשתמש.

ביטול טוקן

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

בנוסף, אפליקציות שתומכות בכניסה באמצעות חשבון Apple צריכות להשתמש ב-Sign in with Apple API ל-REST כדי לבטל אסימוני משתמשים.

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

  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();