احراز هویت با استفاده از اپل

می‌توانید با استفاده از Firebase SDK به کاربران خود اجازه دهید با Firebase با استفاده از Apple ID خود احراز هویت کنند تا جریان ورود به سیستم OAuth 2.0 را انجام دهند.

قبل از شروع

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

به برنامه توسعه دهندگان اپل بپیوندید

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

پیکربندی ورود با اپل

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

اپل را به عنوان ارائه دهنده ورود فعال کنید

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

الزامات داده های ناشناس اپل را رعایت کنید

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

این شامل دریافت هرگونه رضایت کاربر مورد نیاز قبل از مرتبط کردن هر گونه اطلاعات شخصی شناسایی مستقیم با یک شناسه ناشناس اپل است. هنگام استفاده از Firebase Authentication، این ممکن است شامل اقدامات زیر باشد:

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

فهرست فوق کامل نیست. به توافقنامه مجوز برنامه توسعه‌دهنده اپل در بخش عضویت حساب توسعه‌دهنده خود مراجعه کنید تا مطمئن شوید برنامه شما با الزامات اپل مطابقت دارد.

با اپل وارد شوید و با Firebase احراز هویت کنید

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

  1. برای هر درخواست ورود به سیستم، یک رشته تصادفی ایجاد کنید - یک "nonce" - که از آن استفاده می کنید تا مطمئن شوید که رمز شناسه دریافتی به طور خاص در پاسخ به درخواست احراز هویت برنامه شما اعطا شده است. این مرحله برای جلوگیری از حملات تکراری مهم است.

    می توانید با SecRandomCopyBytes(_:_:_) یک nonce امن رمزنگاری ایجاد کنید، مانند مثال زیر:

    سویفت

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

    هدف-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 nonce را همراه با درخواست ورود خود ارسال می‌کنید که اپل در پاسخ آن را بدون تغییر ارسال می‌کند. Firebase پاسخ را با هش کردن nonce اصلی و مقایسه آن با مقدار ارسال شده توسط 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
    }
    
        

    هدف-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. جریان ورود به سیستم اپل را شروع کنید، از جمله در درخواست خود هش SHA256 از nonce و کلاس نماینده که پاسخ اپل را مدیریت می کند (مرحله بعدی را ببینید):

    سویفت

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

    هدف-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. در اجرای ASAuthorizationControllerDelegate ، پاسخ اپل را مدیریت کنید. اگر ورود به سیستم موفقیت آمیز بود، برای احراز هویت با 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)")
      }
    
    }
    

    هدف-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، اپل URL عکس ارائه نمی کند.

همچنین، وقتی کاربر تصمیم می‌گیرد ایمیل خود را با برنامه به اشتراک نگذارد، اپل یک آدرس ایمیل منحصربه‌فرد برای آن کاربر ارائه می‌کند (به شکل xyz@privaterelay.appleid.com )، که آن را با برنامه شما به اشتراک می‌گذارد. اگر سرویس ارسال ایمیل خصوصی را پیکربندی کرده باشید، اپل ایمیل های ارسال شده به آدرس ناشناس را به آدرس ایمیل واقعی کاربر فوروارد می کند.

احراز هویت مجدد و پیوند دادن حساب

از همین الگو می‌توان با 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.
  // ...
}

هدف-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() برای پیوند دادن ارائه دهندگان هویت مختلف به حساب های موجود استفاده کنید.

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

ورود به سیستم با اپل به شما اجازه استفاده مجدد از اعتبارنامه اعتباری برای پیوند دادن به یک حساب موجود را نمی دهد. اگر می‌خواهید یک Sign in با اعتبار اپل را به حساب دیگری پیوند دهید، ابتدا باید سعی کنید حساب‌ها را با استفاده از Sign in قدیمی با اعتبار Apple پیوند دهید و سپس خطای بازگشتی را بررسی کنید تا یک اعتبار جدید پیدا کنید. اعتبار جدید در فرهنگ لغت userInfo خطا قرار می گیرد و می توان از طریق کلید AuthErrorUserInfoUpdatedCredentialKey به آن دسترسی داشت.

به عنوان مثال، برای پیوند دادن یک حساب فیس بوک به حساب فعلی Firebase، از نشانه دسترسی که از ورود کاربر به فیس بوک دریافت کرده اید استفاده کنید:

سویفت

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

هدف-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.
  // ...
}];

ابطال توکن

اپل می‌خواهد برنامه‌هایی که از ایجاد حساب پشتیبانی می‌کنند باید به کاربران اجازه دهند تا حساب خود را در برنامه حذف کنند، همانطور که در دستورالعمل‌های بررسی App Store توضیح داده شده است.

برای برآوردن این نیاز، مراحل زیر را اجرا کنید:

  1. همانطور که در بخش پیکربندی ورود به سیستم با اپل توضیح داده شده است، مطمئن شوید که بخش پیکربندی جریان کد شناسه خدمات و OAuth در پیکربندی Sign in with Apple provider را پر کرده اید.

  2. از آنجایی که Firebase هنگام ایجاد کاربران با Sign in with 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 Database و Cloud Storage خود، می‌توانید شناسه کاربری منحصر به فرد کاربر واردشده به سیستم را از متغیر auth دریافت کنید و از آن برای کنترل داده‌هایی که کاربر می‌تواند به آن دسترسی داشته باشد استفاده کنید.

می‌توانید به کاربران اجازه دهید با استفاده از چندین ارائه‌دهنده احراز هویت، با پیوند دادن اعتبار ارائه‌دهنده تأیید اعتبار به یک حساب کاربری موجود، به برنامه شما وارد شوند.

برای خروج از سیستم یک کاربر، با signOut: .

سویفت

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

هدف-C

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

همچنین ممکن است بخواهید کد رسیدگی به خطا را برای طیف کامل خطاهای احراز هویت اضافه کنید. به رسیدگی به خطاها مراجعه کنید.