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

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

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

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

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

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

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

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

  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, פותחים את הקטע אימות. בכרטיסייה שיטת כניסה, מפעילים את הספק Apple. מציינים את מזהה השירות שיצרתם בקטע הקודם. בנוסף, בקטע OAuth code flow configuration, מציינים את מזהה הצוות ב-Apple ואת המפתח הפרטי ומזהה המפתח שיצרתם בקטע הקודם.

תאימות לדרישות של Apple לגבי נתונים אנונימיים

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

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

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

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

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

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

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

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

    Kotlin+KTX

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

    Java

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

    Kotlin+KTX

    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+KTX

    // 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+KTX

    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+KTX

    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+KTX

// 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+KTX

// 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 באמצעות באמצעות ה-SDK של Apple Sign-In JS, באמצעות OAuth או באמצעות ספריית OAuth AppAuth.

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

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

    Kotlin+KTX

    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+KTX

    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+KTX

    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+KTX

    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 צריכות להשתמש ב-Sign in with Apple API ל-REST כדי לבטל אסימוני משתמשים.

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

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

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

    Kotlin+KTX

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

    Java

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

    Kotlin+KTX

    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+KTX

    Firebase.auth.signOut()

    Java

    FirebaseAuth.getInstance().signOut();