Bạn có thể cho phép người dùng xác thực bằng Firebase bằng Apple ID của họ bằng cách sử dụng Firebase SDK để thực hiện quy trình đăng nhập OAuth 2.0 từ đầu đến cuối.
Trước khi bắt đầu
Để đăng nhập người dùng bằng Apple, trước tiên, hãy định cấu hình tính năng Đăng nhập bằng Apple trên trang web dành cho nhà phát triển của Apple, sau đó bật Apple làm nhà cung cấp dịch vụ đăng nhập cho dự án Firebase của bạn.
Tham gia Chương trình Nhà phát triển của Apple
Chỉ thành viên của Chương trình Nhà phát triển của Apple mới có thể thiết lập tính năng Đăng nhập bằng Apple.
Định cấu hình tính năng Đăng nhập bằng Apple
- Bật tính năng Đăng nhập bằng Apple cho ứng dụng của bạn trên trang Certificates, Identifiers & Profiles (Chứng chỉ, giá trị nhận dạng và hồ sơ) của trang web dành cho nhà phát triển của Apple.
- Liên kết trang web với ứng dụng của bạn như mô tả trong phần đầu tiên của bài viết Định cấu hình tính năng Đăng nhập bằng Apple cho web. Khi được nhắc, hãy đăng ký URL sau đây làm URL trả về:
Bạn có thể lấy mã dự án Firebase trên trang cài đặt bảng điều khiển Firebase. Sau khi hoàn tất, hãy ghi lại mã dịch vụ mới để dùng trong phần tiếp theo.https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
- Tạo khoá riêng tư Đăng nhập bằng Apple. Bạn sẽ cần khoá riêng tư và mã khoá mới trong phần tiếp theo.
- Nếu bạn sử dụng bất kỳ tính năng nào của Firebase Authentication để gửi email cho người dùng, bao gồm cả tính năng đăng nhập bằng đường liên kết qua email, xác minh địa chỉ email, thu hồi thay đổi tài khoản và các tính năng khác, hãy định cấu hình dịch vụ chuyển tiếp email riêng tư của Apple và đăng ký
noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com
(hoặc miền mẫu email tuỳ chỉnh của bạn) để Apple có thể chuyển tiếp email do Firebase Authentication gửi đến địa chỉ email ẩn danh của Apple.
Bật Apple làm nhà cung cấp dịch vụ đăng nhập
- Thêm Firebase vào dự án Apple. Hãy nhớ đăng ký mã nhận dạng gói của ứng dụng khi bạn thiết lập ứng dụng trong bảng điều khiển Firebase.
- Trong bảng điều khiển Firebase, hãy mở mục Auth (Xác thực). Trên thẻ Phương thức đăng nhập, hãy bật trình cung cấp Apple. Chỉ định Mã dịch vụ mà bạn đã tạo trong phần trước. Ngoài ra, trong phần cấu hình luồng mã OAuth, hãy chỉ định mã nhóm Apple, khoá riêng tư và mã khoá mà bạn đã tạo trong phần trước.
Tuân thủ các yêu cầu của Apple về dữ liệu ẩn danh
Tính năng Đăng nhập bằng Apple cho phép người dùng ẩn danh hoá dữ liệu của họ, bao gồm cả địa chỉ email, khi đăng nhập. Người dùng chọn lựa chọn này sẽ có địa chỉ email có miền privaterelay.appleid.com
. Khi sử dụng tính năng Đăng nhập bằng Apple trong ứng dụng của mình, bạn phải tuân thủ mọi chính sách hoặc điều khoản hiện hành của Apple dành cho nhà phát triển liên quan đến các Apple ID ẩn danh này.
Điều này bao gồm việc lấy mọi sự đồng ý bắt buộc của người dùng trước khi bạn liên kết bất kỳ thông tin nhận dạng cá nhân trực tiếp nào với một Apple ID ẩn danh. Khi sử dụng Xác thực Firebase, điều này có thể bao gồm các thao tác sau:
- Liên kết địa chỉ email với một ID Apple ẩn danh hoặc ngược lại.
- Liên kết số điện thoại với một ID Apple ẩn danh hoặc ngược lại
- Liên kết thông tin đăng nhập mạng xã hội không ẩn danh (Facebook, Google, v.v.) với một Apple ID ẩn danh hoặc ngược lại.
Danh sách bên trên chưa đầy đủ. Hãy tham khảo Thoả thuận cấp phép của Chương trình dành cho nhà phát triển của Apple trong phần Thành viên của tài khoản nhà phát triển để đảm bảo ứng dụng của bạn đáp ứng các yêu cầu của Apple.
Đăng nhập bằng Apple và xác thực bằng Firebase
Để xác thực bằng tài khoản Apple, trước tiên, hãy đăng nhập người dùng vào tài khoản Apple của họ bằng cách sử dụng khung AuthenticationServices
của Apple, sau đó dùng mã thông báo nhận dạng trong phản hồi của Apple để tạo một đối tượng AuthCredential
của Firebase:
Đối với mỗi yêu cầu đăng nhập, hãy tạo một chuỗi ngẫu nhiên (một "số chỉ dùng một lần") mà bạn sẽ dùng để đảm bảo mã thông báo nhận dạng bạn nhận được được cấp cụ thể để phản hồi yêu cầu xác thực của ứng dụng. Bước này rất quan trọng để ngăn chặn các cuộc tấn công phát lại.
Bạn có thể tạo một số chỉ dùng một lần an toàn về mật mã bằng
SecRandomCopyBytes(_:_:_)
, như trong ví dụ sau: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]; }
Bạn sẽ gửi hàm băm SHA256 của giá trị chỉ dùng một lần cùng với yêu cầu đăng nhập. Apple sẽ chuyển hàm băm này mà không thay đổi trong phản hồi. Firebase xác thực phản hồi bằng cách băm số chỉ dùng một lần ban đầu và so sánh số đó với giá trị do Apple truyền.
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; }
Bắt đầu quy trình đăng nhập của Apple, bao gồm cả việc đưa vào yêu cầu hàm băm SHA256 của giá trị chỉ dùng một lần và lớp uỷ quyền sẽ xử lý phản hồi của Apple (xem bước tiếp theo):
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]; }
Xử lý phản hồi của Apple trong quá trình triển khai
ASAuthorizationControllerDelegate
. Nếu đăng nhập thành công, hãy sử dụng mã thông báo nhận dạng trong phản hồi của Apple cùng với số chỉ dùng một lần chưa băm để xác thực bằng 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); }
Không giống như các nhà cung cấp khác được Firebase Auth hỗ trợ, Apple không cung cấp URL ảnh.
Ngoài ra, khi người dùng chọn không chia sẻ email của họ với ứng dụng, Apple sẽ cung cấp một địa chỉ email duy nhất cho người dùng đó (theo dạng xyz@privaterelay.appleid.com
) và chia sẻ địa chỉ này với ứng dụng của bạn. Nếu bạn đã định cấu hình dịch vụ chuyển tiếp email riêng tư, Apple sẽ chuyển tiếp email gửi đến địa chỉ ẩn danh đến địa chỉ email thực của người dùng.
Xác thực lại và liên kết tài khoản
Bạn có thể sử dụng cùng một mẫu với reauthenticateWithCredential()
. Bạn có thể dùng mẫu này để truy xuất thông tin đăng nhập mới cho các thao tác nhạy cảm yêu cầu đăng nhập gần đây:
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.
// ...
}];
Ngoài ra, bạn có thể dùng linkWithCredential()
để liên kết các nhà cung cấp danh tính khác nhau với các tài khoản hiện có.
Xin lưu ý rằng Apple yêu cầu bạn phải nhận được sự đồng ý rõ ràng của người dùng trước khi liên kết tài khoản Apple của họ với dữ liệu khác.
Tính năng Đăng nhập bằng Apple sẽ không cho phép bạn dùng lại thông tin xác thực để liên kết với một tài khoản hiện có. Nếu muốn liên kết thông tin đăng nhập bằng Apple với một tài khoản khác, trước tiên, bạn phải thử liên kết các tài khoản bằng thông tin đăng nhập cũ bằng Apple, sau đó kiểm tra lỗi trả về để tìm thông tin đăng nhập mới.
Thông tin đăng nhập mới sẽ nằm trong từ điển userInfo
của lỗi và có thể truy cập thông qua khoá AuthErrorUserInfoUpdatedCredentialKey
.
Ví dụ: để liên kết một tài khoản Facebook với tài khoản Firebase hiện tại, hãy sử dụng mã truy cập mà bạn nhận được khi đăng nhập người dùng vào 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.
// ...
}];
Thu hồi mã thông báo
Apple yêu cầu những ứng dụng hỗ trợ quy trình tạo tài khoản phải cho phép người dùng bắt đầu xoá tài khoản của họ ngay trong ứng dụng, như mô tả trong Nguyên tắc đánh giá của App Store
Để đáp ứng yêu cầu này, hãy triển khai các bước sau:
Đảm bảo bạn đã điền vào phần Mã nhận dạng dịch vụ và Cấu hình quy trình mã OAuth của cấu hình nhà cung cấp Đăng nhập bằng Apple, như được nêu trong phần Định cấu hình tính năng Đăng nhập bằng Apple.
Vì Firebase không lưu trữ mã thông báo người dùng khi người dùng được tạo bằng tính năng Đăng nhập bằng Apple, nên bạn phải yêu cầu người dùng đăng nhập lại trước khi thu hồi mã thông báo của họ và xoá tài khoản.
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) } }
Lấy mã uỷ quyền từ
ASAuthorizationAppleIDCredential
và dùng mã này để gọiAuth.auth().revokeToken(withAuthorizationCode:)
nhằm thu hồi mã thông báo của người dùng.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) } } }
Cuối cùng, hãy xoá tài khoản người dùng (và tất cả dữ liệu liên kết)
Các bước tiếp theo
Sau khi người dùng đăng nhập lần đầu tiên, một tài khoản người dùng mới sẽ được tạo và liên kết với thông tin đăng nhập (tức là tên người dùng và mật khẩu, số điện thoại hoặc thông tin nhà cung cấp dịch vụ uỷ quyền) mà người dùng đã đăng nhập. Tài khoản mới này được lưu trữ trong dự án Firebase của bạn và có thể dùng để xác định một người dùng trên mọi ứng dụng trong dự án, bất kể người dùng đăng nhập bằng cách nào.
-
Trong các ứng dụng của mình, bạn có thể lấy thông tin cơ bản về hồ sơ của người dùng từ đối tượng
User
. Hãy xem phần Quản lý người dùng. Trong Firebase Realtime Database và Cloud Storage Quy tắc bảo mật, bạn có thể lấy mã nhận dạng người dùng riêng biệt của người dùng đã đăng nhập từ biến
auth
và dùng mã nhận dạng này để kiểm soát dữ liệu mà người dùng có thể truy cập.
Bạn có thể cho phép người dùng đăng nhập vào ứng dụng của bạn bằng nhiều trình cung cấp dịch vụ xác thực bằng cách liên kết thông tin đăng nhập của trình cung cấp dịch vụ xác thực với một tài khoản người dùng hiện có.
Để đăng xuất người dùng, hãy gọi
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; }
Bạn cũng có thể muốn thêm mã xử lý lỗi cho toàn bộ các lỗi xác thực. Xem phần Xử lý lỗi.