يمكنك السماح للمستخدمين بالمصادقة مع Firebase باستخدام معرّف Apple الخاص بهم، وذلك من خلال استخدام حزمة تطوير البرامج (SDK) لمنصّة Firebase لتسجيل الدخول من خلال بروتوكول OAuth 2.0 الشامل.
قبل البدء
لتسجيل دخول المستخدمين باستخدام Apple، يجب أولاً ضبط ميزة "تسجيل الدخول باستخدام حساب Apple" على الموقع الإلكتروني للمطوّرين من Apple، ثم تفعيل Apple باعتبارها مقدّم خدمة تسجيل الدخول لمشروعك على Firebase.
الانضمام إلى برنامج مطوّري برامج Apple
لا يمكن إعداد ميزة "تسجيل الدخول باستخدام حساب Apple" إلّا من خلال أعضاء برنامج مطوّري Apple.
ضبط ميزة "تسجيل الدخول باستخدام حساب Apple"
- فعِّل ميزة "تسجيل الدخول باستخدام حساب Apple" لتطبيقك في صفحة الشهادات والمعرّفات والملفات الشخصية على الموقع الإلكتروني للمطوّرين من Apple.
- اربط موقعك الإلكتروني بتطبيقك كما هو موضّح في القسم الأول من
ضبط ميزة "تسجيل الدخول باستخدام حساب Apple" للويب. سجِّل
عنوان URL التالي على أنّه عنوان URL للعرض عندما يُطلب منك ذلك:
https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
يمكنك الحصول على رقم تعريف مشروع Firebase من صفحة إعدادات وحدة تحكُّم Firebase. عند الانتهاء، دوِّن معرّف الخدمة الجديد، والذي ستحتاجه في القسم التالي. - أنشئ مفتاحًا خاصًا لتسجيل الدخول باستخدام Apple ستحتاج إلى المفتاح الخاص الجديد ورقم تعريف المفتاح في القسم التالي.
- إذا كنت تستخدم أيًا من ميزات "مصادقة Firebase" التي ترسل الرسائل الإلكترونية إلى المستخدمين،
بما في ذلك تسجيل الدخول باستخدام رابط البريد الإلكتروني، والتحقق من عنوان البريد الإلكتروني، وإلغاء تغيير الحساب،
وغيرها، يمكنك ضبط خدمة ترحيل البريد الإلكتروني الخاص في Apple
والتسجيل
noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com
(أو نطاق نموذج البريد الإلكتروني المخصص) كي تتمكن Apple من ترحيل الرسائل الإلكترونية المُرسَلة من خلال مصادقة Firebase إلى عناوين بريد إلكتروني مخفية الهوية على Apple.
تفعيل Apple كموفِّر لتسجيل الدخول
- إضافة Firebase إلى مشروع Apple يجب تسجيل رقم تعريف حزمة التطبيق عند إعداد التطبيق في وحدة تحكُّم Firebase.
- في وحدة تحكُّم 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:
لكل طلب تسجيل دخول، أنشِئ سلسلة عشوائية، "بدون"، ستستخدمها للتأكد من أنّ الرمز المميّز للمعرّف الذي حصلت عليه قد تم منحه خصيصًا استجابةً لطلب المصادقة في تطبيقك. هذه الخطوة مهمة لمنع هجمات إعادة التشغيل.
يمكنك إنشاء رقم هاتف غير آمن تشفيريًا باستخدام
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; }
ابدأ خطوات تسجيل الدخول إلى 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]; }
التعامل مع ردّ 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
لاستيفاء هذا الشرط، اتّبِع الخطوات التالية:
يجب التأكّد من ملء قسم معرّف الخدمات وضبط مسار رمز OAuth ضمن إعدادات موفّر خدمة "تسجيل الدخول باستخدام Apple"، كما هو موضّح في القسم ضبط تسجيل الدخول باستخدام Apple.
بما أنّ 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) } }
احصل على رمز التفويض من
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) } } }
أخيرًا، احذف حساب المستخدم (وجميع البيانات المرتبطة به)
الخطوات اللاحقة
بعد تسجيل دخول المستخدم للمرة الأولى، يتم إنشاء حساب مستخدم جديد وربطه ببيانات الاعتماد، أي اسم المستخدم وكلمة المرور، أو رقم الهاتف، أو معلومات مقدم خدمة المصادقة. يتم تخزين هذا الحساب الجديد كجزء من مشروعك في 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; }
يمكنك أيضًا إضافة رمز معالجة الأخطاء إلى المجموعة الكاملة من أخطاء المصادقة. يُرجى الاطّلاع على التعامل مع الأخطاء.