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

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

قبل ان تبدأ

لتسجيل دخول المستخدمين باستخدام Apple، قم أولاً بتكوين تسجيل الدخول باستخدام Apple على موقع مطور Apple، ثم قم بتمكين Apple كموفر تسجيل دخول لمشروع Firebase الخاص بك.

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

لا يمكن تكوين تسجيل الدخول باستخدام Apple إلا من خلال أعضاء برنامج Apple Developer .

تكوين تسجيل الدخول مع 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 Developer في قسم العضوية في حساب المطور الخاص بك للتأكد من أن تطبيقك يلبي متطلبات Apple.

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

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

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

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

    سويفت

    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)
    }
    
        

    ج موضوعية

    // 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.

    سويفت

    @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
    }
    
        

    ج موضوعية

    - (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 (راجع الخطوة التالية):

    سويفت

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

    ج موضوعية

    @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:

    سويفت

    @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)")
      }
    
    }
    

    ج موضوعية

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

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

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

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

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

سويفت

// 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.
  // ...
}

ج موضوعية

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:

سويفت

// 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.
  // ...
}

ج موضوعية

// 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 أن تسمح التطبيقات التي تدعم إنشاء الحساب للمستخدمين ببدء حذف حساباتهم داخل التطبيق، كما هو موضح في إرشادات مراجعة متجر التطبيقات

لتلبية هذا المطلب، قم بتنفيذ الخطوات التالية:

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

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

    سويفت

    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:) لإلغاء الرموز المميزة للمستخدم.

    سويفت

    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 Realtime وقواعد أمان التخزين السحابي، يمكنك الحصول على معرف المستخدم الفريد للمستخدم الذي قام بتسجيل الدخول من متغير auth ، واستخدامه للتحكم في البيانات التي يمكن للمستخدم الوصول إليها.

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

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

سويفت

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

ج موضوعية

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

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