المصادقة باستخدام Apple

يمكنك السماح للمستخدمين بالمصادقة مع Firebase باستخدام معرّف Apple الخاص بهم، وذلك من خلال استخدام حزمة تطوير البرامج (SDK) لمنصّة Firebase لتسجيل الدخول من خلال بروتوكول OAuth 2.0 الشامل.

قبل البدء

لتسجيل دخول المستخدمين باستخدام Apple، يجب أولاً ضبط ميزة "تسجيل الدخول باستخدام حساب Apple" على الموقع الإلكتروني للمطوّرين من Apple، ثم تفعيل Apple باعتبارها مقدّم خدمة تسجيل الدخول لمشروعك على Firebase.

الانضمام إلى برنامج مطوّري برامج Apple

لا يمكن إعداد ميزة "تسجيل الدخول باستخدام حساب Apple" إلّا من خلال أعضاء برنامج مطوّري Apple.

ضبط ميزة "تسجيل الدخول باستخدام حساب Apple"

  1. فعِّل ميزة "تسجيل الدخول باستخدام حساب Apple" لتطبيقك في صفحة الشهادات والمعرّفات والملفات الشخصية على الموقع الإلكتروني للمطوّرين من Apple.
  2. اربط موقعك الإلكتروني بتطبيقك كما هو موضّح في القسم الأول من ضبط ميزة "تسجيل الدخول باستخدام حساب Apple" للويب. سجِّل عنوان URL التالي على أنّه عنوان URL للعرض عندما يُطلب منك ذلك:
    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
    يمكنك الحصول على رقم تعريف مشروع Firebase من صفحة إعدادات وحدة تحكُّم Firebase. عند الانتهاء، دوِّن معرّف الخدمة الجديد، والذي ستحتاجه في القسم التالي.
  3. أنشئ مفتاحًا خاصًا لتسجيل الدخول باستخدام Apple ستحتاج إلى المفتاح الخاص الجديد ورقم تعريف المفتاح في القسم التالي.
  4. إذا كنت تستخدم أيًا من ميزات "مصادقة Firebase" التي ترسل الرسائل الإلكترونية إلى المستخدمين، بما في ذلك تسجيل الدخول باستخدام رابط البريد الإلكتروني، والتحقق من عنوان البريد الإلكتروني، وإلغاء تغيير الحساب، وغيرها، يمكنك ضبط خدمة ترحيل البريد الإلكتروني الخاص في Apple والتسجيل noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (أو نطاق نموذج البريد الإلكتروني المخصص) كي تتمكن Apple من ترحيل الرسائل الإلكترونية المُرسَلة من خلال مصادقة Firebase إلى عناوين بريد إلكتروني مخفية الهوية على Apple.

تفعيل Apple كموفِّر لتسجيل الدخول

  1. إضافة Firebase إلى مشروع Apple يجب تسجيل رقم تعريف حزمة التطبيق عند إعداد التطبيق في وحدة تحكُّم Firebase.
  2. في وحدة تحكُّم Firebase، افتح قسم المصادقة. في علامة التبويب طريقة تسجيل الدخول، فعِّل موفّر خدمة Apple. حدِّد رقم تعريف الخدمة الذي أنشأته في القسم السابق. بالإضافة إلى ذلك، في قسم إعداد مسار رمز OAuth، حدِّد رقم تعريف فريق Apple والمفتاح الخاص ورقم تعريف المفتاح اللذين أنشأتهما في القسم السابق.

الالتزام بمتطلبات البيانات المخفية الهوية من Apple

تتيح ميزة "تسجيل الدخول باستخدام حساب Apple" للمستخدمين خيار إخفاء هوية بياناتهم، بما في ذلك عناوين بريدهم الإلكتروني، عند تسجيل الدخول. المستخدمون الذين يحددون هذا الخيار لديهم عناوين بريد إلكتروني بالنطاق privaterelay.appleid.com. عند استخدام ميزة "تسجيل الدخول باستخدام حساب Apple" في تطبيقك، عليك الالتزام بأيّ سياسات سارية للمطوّرين أو بنود من Apple في ما يتعلّق بمعرّفات Apple المجهولة الهوية هذه.

ويشمل ذلك الحصول على أي موافقة مطلوبة من المستخدم قبل ربط أي معلومات شخصية تحدّد الهوية بشكل مباشر بمعرّف Apple مخفي الهوية. عند استخدام مصادقة Firebase، قد يتضمن ذلك الإجراءات التالية:

  • ربط عنوان بريد إلكتروني بمعرّف Apple مجهول الهوية أو العكس بالعكس.
  • ربط رقم هاتف بمعرّف Apple مجهول الهوية أو العكس
  • ربط بيانات اعتماد غير مجهولة الهوية على وسائل التواصل الاجتماعي (Facebook أو Google وغير ذلك) بحساب Apple مجهول الهوية أو العكس

إنّ القائمة أعلاه ليست شاملة. يُرجى الرجوع إلى "اتفاقية ترخيص برنامج مطوّري البرامج من Apple" في قسم "الاشتراك" في حساب المطوِّر للتأكّد من أنّ تطبيقك يستوفي متطلبات Apple.

تسجيل الدخول باستخدام حساب Apple والمصادقة باستخدام Firebase

للمصادقة باستخدام حساب Apple، يجب أولاً تسجيل دخول المستخدم إلى حسابه على Apple باستخدام إطار عمل AuthenticationServices من Apple، ثم استخدام الرمز المميّز للمعرّف من ردّ Apple لإنشاء عنصر AuthCredential في Firebase:

  1. لكل طلب تسجيل دخول، أنشِئ سلسلة عشوائية، "بدون"، ستستخدمها للتأكد من أنّ الرمز المميّز للمعرّف الذي حصلت عليه قد تم منحه خصيصًا استجابةً لطلب المصادقة في تطبيقك. هذه الخطوة مهمة لمنع هجمات إعادة التشغيل.

    يمكنك إنشاء رقم هاتف غير آمن تشفيريًا باستخدام SecRandomCopyBytes(_:_:_)، كما في المثال التالي:

    Swift

    private func randomNonceString(length: Int = 32) -> String {
      precondition(length > 0)
      var randomBytes = [UInt8](repeating: 0, count: length)
      let errorCode = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes)
      if errorCode != errSecSuccess {
        fatalError(
          "Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)"
        )
      }
    
      let charset: [Character] =
        Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
    
      let nonce = randomBytes.map { byte in
        // Pick a random character from the set, wrapping around if needed.
        charset[Int(byte) % charset.count]
      }
    
      return String(nonce)
    }
    
        

    Objective-C

    // Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-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--;
          }
        }
      }
    
      return [result copy];
    }
        

    سترسِل تجزئة SHA256 من الرقم غير المرغوب فيه مع طلب تسجيل الدخول، وستعمل Apple على ضبطها بدون تغيير في الردّ. يتحقق Firebase من صحة الاستجابة من خلال تجزئة الرقم الأصلي ومقارنتها بالقيمة التي تمريرتها Apple.

    Swift

    @available(iOS 13, *)
    private func sha256(_ input: String) -> String {
      let inputData = Data(input.utf8)
      let hashedData = SHA256.hash(data: inputData)
      let hashString = hashedData.compactMap {
        String(format: "%02x", $0)
      }.joined()
    
      return hashString
    }
    
        

    Objective-C

    - (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;
    }
        
  2. ابدأ خطوات تسجيل الدخول إلى Apple، بما في ذلك في طلبك تجزئة SHA256 من nonce والفئة المفوَّضة التي ستتعامل مع رد Apple (راجع الخطوة التالية):

    Swift

    import CryptoKit
    
    // Unhashed nonce.
    fileprivate var currentNonce: String?
    
    @available(iOS 13, *)
    func startSignInWithAppleFlow() {
      let nonce = randomNonceString()
      currentNonce = nonce
      let appleIDProvider = ASAuthorizationAppleIDProvider()
      let request = appleIDProvider.createRequest()
      request.requestedScopes = [.fullName, .email]
      request.nonce = sha256(nonce)
    
      let authorizationController = ASAuthorizationController(authorizationRequests: [request])
      authorizationController.delegate = self
      authorizationController.presentationContextProvider = self
      authorizationController.performRequests()
    }
    

    Objective-C

    @import CommonCrypto;
    
    - (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];
    }
    
  3. التعامل مع ردّ Apple عند تنفيذ ASAuthorizationControllerDelegate إذا تم تسجيل الدخول بنجاح، استخدِم الرمز المميّز للمعرّف من استجابة Apple مع رقم غير مجزأ للمصادقة مع Firebase:

    Swift

    @available(iOS 13.0, *)
    extension MainViewController: ASAuthorizationControllerDelegate {
    
      func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
          guard let nonce = currentNonce else {
            fatalError("Invalid state: A login callback was received, but no login request was sent.")
          }
          guard let appleIDToken = appleIDCredential.identityToken else {
            print("Unable to fetch identity token")
            return
          }
          guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
            print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
            return
          }
          // Initialize a Firebase credential, including the user's full name.
          let credential = OAuthProvider.appleCredential(withIDToken: idTokenString,
                                                            rawNonce: nonce,
                                                            fullName: appleIDCredential.fullName)
          // Sign in with Firebase.
          Auth.auth().signIn(with: credential) { (authResult, error) in
            if error {
              // Error. If error.code == .MissingOrInvalidNonce, make sure
              // you're sending the SHA256-hashed nonce as a hex string with
              // your request to Apple.
              print(error.localizedDescription)
              return
            }
            // User is signed in to Firebase with Apple.
            // ...
          }
        }
      }
    
      func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        // Handle error.
        print("Sign in with Apple errored: \(error)")
      }
    
    }
    

    Objective-C

    - (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);
        }
    
        // Initialize a Firebase credential, including the user's full name.
        FIROAuthCredential *credential = [FIROAuthProvider appleCredentialWithIDToken:IDToken
                                                                             rawNonce:self.appleRawNonce
                                                                             fullName:appleIDCredential.fullName];
    
        // Sign in with Firebase.
        [[FIRAuth auth] signInWithCredential:credential
                                  completion:^(FIRAuthDataResult * _Nullable authResult,
                                               NSError * _Nullable error) {
          if (error != nil) {
            // Error. If error.code == FIRAuthErrorCodeMissingOrInvalidNonce,
            // make sure you're sending the SHA256-hashed nonce as a hex string
            // with your request to Apple.
            return;
          }
          // Sign-in succeeded!
        }];
      }
    }
    
    - (void)authorizationController:(ASAuthorizationController *)controller
               didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) {
      NSLog(@"Sign in with Apple errored: %@", error);
    }
    

لا توفّر Apple عنوان URL للصورة، على عكس مقدّمي الخدمة الآخرين المعتمَدين من خلال مصادقة Firebase.

بالإضافة إلى ذلك، عندما يختار المستخدم عدم مشاركة عنوان بريده الإلكتروني مع التطبيق، ستوفّر Apple عنوان بريد إلكتروني فريدًا لهذا المستخدم (من النموذج xyz@privaterelay.appleid.com) تتم مشاركته مع التطبيق. إذا ضبطت خدمة ترحيل البريد الإلكتروني الخاص، تعيد Apple توجيه الرسائل الإلكترونية المُرسَلة إلى العنوان المجهول الهوية إلى عنوان البريد الإلكتروني الحقيقي للمستخدم.

إعادة المصادقة وربط الحساب

يمكن استخدام النمط نفسه مع reauthenticateWithCredential()، ويمكنك استخدامه لاسترداد بيانات اعتماد جديدة للعمليات الحساسة التي تتطلّب تسجيل دخول حديث:

Swift

// Initialize a fresh Apple credential with Firebase.
let credential = OAuthProvider.credential(
  withProviderID: "apple.com",
  IDToken: appleIdToken,
  rawNonce: rawNonce
)
// Reauthenticate current Apple user with fresh Apple credential.
Auth.auth().currentUser.reauthenticate(with: credential) { (authResult, error) in
  guard error != nil else { return }
  // Apple user successfully re-authenticated.
  // ...
}

Objective-C

FIRAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com",
                                                                   IDToken:appleIdToken,
                                                                  rawNonce:rawNonce];
[[FIRAuth auth].currentUser
    reauthenticateWithCredential:credential
                      completion:^(FIRAuthDataResult * _Nullable authResult,
                                   NSError * _Nullable error) {
  if (error) {
    // Handle error.
  }
  // Apple user successfully re-authenticated.
  // ...
}];

ويمكنك استخدام linkWithCredential() لربط موفِّري هوية مختلفين بحسابات حالية.

ملاحظة: تشترط Apple الحصول على موافقة صريحة من المستخدمين قبل ربط حساباتهم على Apple ببيانات أخرى.

لن تتيح لك ميزة "تسجيل الدخول باستخدام حساب Apple" إعادة استخدام بيانات اعتماد المصادقة للربط بحساب حالي. إذا أردت ربط بيانات اعتماد تسجيل الدخول باستخدام Apple بحساب آخر، يجب أولاً محاولة ربط الحسابات باستخدام بيانات اعتماد تسجيل الدخول القديمة في Apple، ثم فحص الخطأ الذي ظهر للعثور على بيانات اعتماد جديدة. ستظهر بيانات الاعتماد الجديدة في قاموس الخطأ userInfo ويمكن الوصول إليها من خلال المفتاح AuthErrorUserInfoUpdatedCredentialKey.

على سبيل المثال، لربط حساب Facebook بحساب Firebase الحالي، استخدِم رمز الدخول الذي حصلت عليه من تسجيل دخول المستخدم إلى Facebook:

Swift

// Initialize a Facebook credential with Firebase.
let credential = FacebookAuthProvider.credential(
  withAccessToken: AccessToken.current!.tokenString
)
// Assuming the current user is an Apple user linking a Facebook provider.
Auth.auth().currentUser.link(with: credential) { (authResult, error) in
  // Facebook credential is linked to the current Apple user.
  // The user can now sign in with Facebook or Apple to the same Firebase
  // account.
  // ...
}

Objective-C

// Initialize a Facebook credential with Firebase.
FacebookAuthCredential *credential = [FIRFacebookAuthProvider credentialWithAccessToken:accessToken];
// Assuming the current user is an Apple user linking a Facebook provider.
[FIRAuth.auth linkWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
  // Facebook credential is linked to the current Apple user.
  // The user can now sign in with Facebook or Apple to the same Firebase
  // account.
  // ...
}];

إبطال الرمز المميّز

تشترط Apple على التطبيقات التي تتيح إنشاء الحسابات أن تسمح للمستخدمين ببدء حذف حساباتهم داخل التطبيق، كما هو موضّح في إرشادات مراجعة App Store

لاستيفاء هذا الشرط، اتّبِع الخطوات التالية:

  1. يجب التأكّد من ملء قسم معرّف الخدمات وضبط مسار رمز OAuth ضمن إعدادات موفّر خدمة "تسجيل الدخول باستخدام Apple"، كما هو موضّح في القسم ضبط تسجيل الدخول باستخدام Apple.

  2. بما أنّ Firebase لا يخزِّن الرموز المميزة للمستخدمين عند إنشاء المستخدمين من خلال ميزة "تسجيل الدخول باستخدام حساب Apple"، يجب أن تطلب من المستخدم تسجيل الدخول مرة أخرى قبل إلغاء رمزه المميّز وحذف الحساب.

    Swift

    private func deleteCurrentUser() {
      do {
        let nonce = try CryptoUtils.randomNonceString()
        currentNonce = nonce
        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let request = appleIDProvider.createRequest()
        request.requestedScopes = [.fullName, .email]
        request.nonce = CryptoUtils.sha256(nonce)
    
        let authorizationController = ASAuthorizationController(authorizationRequests: [request])
        authorizationController.delegate = self
        authorizationController.presentationContextProvider = self
        authorizationController.performRequests()
      } catch {
        // In the unlikely case that nonce generation fails, show error view.
        displayError(error)
      }
    }
    
    
  3. احصل على رمز التفويض من ASAuthorizationAppleIDCredential واستخدِمه لطلب Auth.auth().revokeToken(withAuthorizationCode:) لإبطال الرموز المميّزة الخاصة بالمستخدم.

    Swift

    func authorizationController(controller: ASAuthorizationController,
                                 didCompleteWithAuthorization authorization: ASAuthorization) {
      guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential
      else {
        print("Unable to retrieve AppleIDCredential")
        return
      }
    
      guard let _ = currentNonce else {
        fatalError("Invalid state: A login callback was received, but no login request was sent.")
      }
    
      guard let appleAuthCode = appleIDCredential.authorizationCode else {
        print("Unable to fetch authorization code")
        return
      }
    
      guard let authCodeString = String(data: appleAuthCode, encoding: .utf8) else {
        print("Unable to serialize auth code string from data: \(appleAuthCode.debugDescription)")
        return
      }
    
      Task {
        do {
          try await Auth.auth().revokeToken(withAuthorizationCode: authCodeString)
          try await user?.delete()
          self.updateUI()
        } catch {
          self.displayError(error)
        }
      }
    }
    
    
  4. أخيرًا، احذف حساب المستخدم (وجميع البيانات المرتبطة به)

الخطوات اللاحقة

بعد تسجيل دخول المستخدم للمرة الأولى، يتم إنشاء حساب مستخدم جديد وربطه ببيانات الاعتماد، أي اسم المستخدم وكلمة المرور، أو رقم الهاتف، أو معلومات مقدم خدمة المصادقة. يتم تخزين هذا الحساب الجديد كجزء من مشروعك في Firebase، ويمكن استخدامه لتحديد مستخدم على مستوى كل تطبيق في مشروعك، بغض النظر عن كيفية تسجيل المستخدم للدخول.

  • في تطبيقاتك، يمكنك الحصول على معلومات الملف الشخصي الأساسية للمستخدم من العنصر User . راجع إدارة المستخدمين.

  • في قاعدة بيانات Firebase في الوقت الفعلي وقواعد أمان Cloud Storage، يمكنك الحصول على رقم تعريف المستخدم الفريد للمستخدم الذي سجّل الدخول من المتغيّر auth واستخدامه للتحكّم في البيانات التي يمكن للمستخدم الوصول إليها.

يمكنك السماح للمستخدمين بتسجيل الدخول إلى تطبيقك باستخدام عدة موفِّري مصادقة من خلال ربط بيانات اعتماد موفِّر المصادقة بحساب مستخدم حالي.

لتسجيل خروج مستخدم، يُرجى الاتصال بالرقم signOut:.

Swift

let firebaseAuth = Auth.auth()
do {
  try firebaseAuth.signOut()
} catch let signOutError as NSError {
  print("Error signing out: %@", signOutError)
}

Objective-C

NSError *signOutError;
BOOL status = [[FIRAuth auth] signOut:&signOutError];
if (!status) {
  NSLog(@"Error signing out: %@", signOutError);
  return;
}

يمكنك أيضًا إضافة رمز معالجة الأخطاء إلى المجموعة الكاملة من أخطاء المصادقة. يُرجى الاطّلاع على التعامل مع الأخطاء.