اصالت‌سنجی بااستفاده از Apple

می‌توانید به کاربران اجازه دهید بااستفاده از Apple ID خودشان با Firebase اصالت‌سنجی کنند، برای این کار باید از «کیت توسعه نرم‌افزار Firebase» برای انجام جریان ورود به سیستم سرتاسر OAuth 2.0 استفاده کنید.

قبل از شروع

برای ورود به سیستم کاربران بااستفاده از Apple، ابتدا «ورود به سیستم با Apple» را در سایت توسعه‌دهنده Apple پیکربندی کنید، سپس Apple را به‌عنوان ارائه‌دهنده ورود به سیستم برای پروژه Firebase خود فعال کنید.

به «برنامه توسعه‌دهندگان Apple» بپیوندید

«ورود به سیستم با Apple» فقط توسط اعضای برنامه توسعه‌دهندگان Apple قابل پیکربندی است.

پیکربندی «ورود به سیستم با Apple»

  1. «ورود به سیستم با Apple» را برای برنامه‌تان در صفحه گواهینامه‌ها، شناسه‌ها، و نمایه‌ها سایت توسعه‌دهندگان Apple فعال کنید.
  2. وب‌سایتتان را همان‌طور که در بخش اول پیکربندی «ورود به سیستم با Apple» برای وب توضیح داده شده است با برنامه‌تان مرتبط کنید. وقتی پیام‌واره نشان داده شد، نشانی وب زیر را به‌عنوان «نشانی وب برگشتی» ثبت کنید:
    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
    می‌توانید شناسه پروژه Firebase خود را در Firebaseصفحه تنظیمات کنسول دریافت کنید. وقتی کارتان تمام شد، «شناسه سرویس» جدیدتان را یادداشت کنید، زیرا در بخش بعدی به آن نیاز خواهید داشت.
  3. ایجاد کلید خصوصی «ورود به سیستم با Apple». در بخش بعدی به کلید خصوصی جدید و شناسه کلید نیاز خواهید داشت.
  4. اگر از هریک از ویژگی‌های Firebase Authentication که ایمیل به کاربران ارسال می‌کند استفاده می‌کنید، ازجمله ورود به سیستم با پیوند ایمیل، درستی‌سنجی نشانی ایمیل، لغو تغییر حساب، و موارد دیگر، خدمات بازارسال ایمیل خصوصی Apple را پیکربندی کنید و noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (یا دامنه الگوی ایمیل سفارشی‌سازی‌شده خودتان) را ثبت کنید تا Apple بتواند ایمیل‌های ارسالی از Firebase Authentication به نشانی‌های ایمیل ناشناس Apple را بازارسال کند.

فعال کردن Apple به‌عنوان ارائه‌دهنده ورود به سیستم

  1. ‫Firebase را به پروژه Apple خود اضافه کنید. حتماً هنگام راه‌اندازی برنامه در کنسول Firebase، شناسه دسته‌ای برنامه‌تان را ثبت کنید.
  2. در کنسول Firebase، به امنیت > اصالت‌سنجی بروید.
  3. در زبانه روش ورود به سیستم، ارائه‌دهنده ورود به سیستم Apple را فعال کنید. «شناسه سرویس» را که در بخش قبلی ایجاد کرده‌اید مشخص کنید. همچنین، در بخش پیکربندی جریان کد OAuth، «شناسه تیم Apple» و کلید خصوصی و شناسه کلیدی را که در بخش قبلی ایجاد کردید مشخص کنید.

از الزامات داده‌های ناشناس‌سازی‌شده Apple پیروی کنید

«ورود به سیستم با Apple» به کاربران این امکان را می‌دهد که هنگام ورود به سیستم، داده‌هایشان، ازجمله نشانی ایمیلشان، را ناشناس کنند. کاربرانی که این گزینه را انتخاب می‌کنند نشانی ایمیل با دامنه privaterelay.appleid.com دارند. وقتی از «ورود به سیستم با Apple» در برنامه‌تان استفاده می‌کنید، باید از همه خط‌مشی‌ها یا شرایط توسعه‌دهنده ذی‌ربط Apple درخصوص این شناسه‌های Apple ناشناس پیروی کنید.

این شامل دریافت رضایت کاربر موردنیاز قبل‌از قراردادن هرگونه اطلاعات شخصی که مستقیماً شناسایی‌کننده است درارتباط با «شناسه Apple» ناشناس‌سازی‌شده می‌شود. هنگام استفاده از «اصالت‌سنجی Firebase»، این ممکن است شامل اقدامات زیر باشد:

  • نشانی ایمیل را به Apple ID ناشناس پیوند دهید یا برعکس.
  • پیوند دادن شماره تلفن به Apple ID ناشناس یا برعکس
  • اعتبار اجتماعی غیرگمنام (Facebook،‏ Google، و غیره) را به Apple ID گمنام‌شده پیوند دهید یا برعکس.

فهرست بالا کامل نیست. برای اطمینان از اینکه برنامه‌تان الزامات Apple را برآورده می‌کند، به «قرارداد پروانه برنامه توسعه‌دهندگان Apple» در بخش «عضویت» حساب توسعه‌دهنده‌تان مراجعه کنید.

ورود به سیستم با Apple و اصالت‌سنجی با Firebase

برای اصالت‌سنجی با حساب Apple، ابتدا کاربر را بااستفاده از چارچوب AuthenticationServices‏ Apple به سیستم حساب Apple خود وارد کنید، و سپس از شناسه توکن در پاسخ Apple برای ایجاد شیء Firebase AuthCredential استفاده کنید:

  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 مقدار یک‌بارمصرف و کلاس نماینده‌ای که پاسخ 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);
    }
    

برخلاف دیگر ارائه‌دهندگان پشتیبانی‌شده توسط Firebase Auth،‏ Apple نشانی وب عکس ارائه نمی‌دهد.

همچنین، وقتی کاربر انتخاب می‌کند ایمیلش را با برنامه هم‌رسانی نکند، 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 var currentNonce: String?
    
    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

    private var user: User?
    
    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 Database و 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;
}

همچنین ممکن است بخواهید کد مدیریت خطا را برای طیف کامل خطاهای اصالت‌سنجی اضافه کنید. مدیریت خطاها را ببینید.