0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Firebase AuthenticationでSign in with Appleを利用する方法

Posted at

iOSアプリ開発において、Firebase AuthenticationでSign in with Appleを利用する機会があったので、その時の設定手順を備忘として記事にしておきます。

前提

Firebaseプロジェクトのセットアップを行なっておく

Firebaseコンソールで、FirebaseプロジェクトをiOSアプリ用にセットアップしておきます
セットアップ方法は公式の手順で十分わかりやすいと思うので、この記事では解説を省略します。
後ほど使うので、公式の手順のステップ4で、FirebaseAuthを追加しておくと良さそうです。

Apple Developer Programに参加しておく

Sign In With Appleを使うにはCapabilityをXcodeのプロジェクトに追加する必要がありますが、これはApple Developer Programに参加しているアカウントが必要になります。
以下のようにXcodeの設定のAccountsに、Apple Developer Programに参加したAppleアカウントが紐づけられていることを確認してから、後述する手順を行ってください。

image.png

Provisioning Profileをアプリに設定しておく

この記事の説明では、Provisioning Profileを設定している前提になります。
設定手順を説明している記事はたくさんあると思うので、ここでは省略します。

個人的には、以下記事がわかりやすかったです。
https://hirokuma.blog/?p=2715

手順

Capabilityの設定

アプリのメインターゲットの"Signing & Capabilities"から、"+ Capability"を押下します
image.png

表示されるポップアップで、"Sign in with Apple"を選択します
image.png

"Sign in with Apple"の表示が追加されていればOK
image.png

Firebase Authenticationの設定

作成しているFirebaseプロジェクトのコンソール画面から、"Authentication"を選択し、"始める"を押下します
image.png

ログイン方法で、"Apple"を選択します
image.png

有効にするのトグルボタンを、押下します。
また、次の手順で使用するので、赤枠のURLは控えておきます。
その後、保存ボタンを押下します。
image.png

以下のように、Appleプロバイダが有効になっていればOKです。
image.png

App IDにSign in with Appleの設定を追加

Apple Developer ProgramのIdentifiersの画面を開きます。
そこで、対象のアプリのApp IDを選択します。(今回の例では"homete for debugを選択します")
image.png

下にスクロールすると、Sign in with Appleの項目があるので、チェックボックをチェックし、Editボタンを押下します。
image.png

赤枠部分に、Firebase Authenticationの設定で控えたURLを入力し、Saveボタンを押下します。
image.png

最後にSaveボタンを押下しApp IDの内容を編集を確定します。
image.png

Provisioning Profileをマニュアル管理している場合は、CapabilityとApp IDの編集でプロジェクトのSigningにエラーが出ると思うので、必要に応じてProvisioning Profileの更新を行ってください。

認証処理を実装

Sign in with AppleでAppleアカウントの認証

SwiftUIでSign in with Apple用のボタンを実装します。(あくまで実装例です)
このボタンをタップした時にSign in with AppleでAppleアカウントの認証を行いますが、この時点ではFirebase側でのログインまでは行っていません。
onSignInのクロージャでSignInUpWithAppleButtonの呼び出し側でFirebaseの認証を行います。

import AuthenticationServices
import SwiftUI
import CryptoKit

struct SignInUpWithAppleButton: View {
    @Environment(\.colorScheme) var colorScheme
    @State var currentNonce: SignInWithAppleNonce?
    
    let onSignIn: (Result<SignInWithAppleResult, any Error>) async -> Void
    
    var body: some View {
        SignInWithAppleButton {
            handleRequest(request: $0)
        } onCompletion: {
            handleCompletion(result: $0)
        }
        .signInWithAppleButtonStyle(colorScheme == .light ? .black : .white)
        .id(colorScheme)
    }
}

private extension SignInUpWithAppleButton {
    
    func handleRequest(request: ASAuthorizationAppleIDRequest) {
        let nonce = generateNonce()
        request.requestedScopes = [.fullName]
        request.nonce = nonce.sha256
        currentNonce = nonce
    }
   
    func handleCompletion(result: Result<ASAuthorization, any Error>) {
        Task {
            switch result {
            case .success(let authorization):
                guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential,
                      let currentNonce else {
                    
                    preconditionFailure("No sent sign in request.")
                }
                guard let appleIDToken = appleIDCredential.identityToken,
                      let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
                    
                    await onSignIn(.failure(DomainError.failAuth))
                    return
                }
                
                let result = SignInWithAppleResult(tokenId: idTokenString, nonce: currentNonce.original)
                await onSignIn(.success(result))
                
            case .failure(let error):
                print("failed sign in with apple: \(error)")
                if let error = error as? ASAuthorizationError,
                   error.code != .canceled {
                    
                    await onSignIn(.failure(DomainError.failAuth))
                }
            }
        }
    }
    
    func generateNonce() -> SignInWithAppleNonce {
        
        var randomBytes = [UInt8](repeating: 0, count: 32)
        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 .init(original: String(nonce), sha256: sha256(String(nonce)))
    }
    
    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
    }
}

struct SignInWithAppleNonce {
    
    let original: String // 暗号化されていないNonce
    let sha256: String // sha256で暗号化されたNonce
}

struct SignInWithAppleResult {
    
    let tokenId: String // Sign in with Appleで取得したトークン
    let nonce: String // Sign in with Appleで使用したNonce
}

Sign in with Apple用のUIはAuthenticationServicesフレームワークで提供されているので、ボタンのUIはそれほどいじれません。
上記例では、signInWithAppleButtonStyleでダークモードであれば白く、そうでなければ黒く表示します。

Appleアカウントの認証情報でFirebaseへログインの処理実装

上記で定義したSignInUpWithAppleButtonのクロージャで、Sign in with Appleによる認証情報を使用して、Firebase Authenticationでログイン処理を行います。

import FirebaseAuth
import SwiftUI

struct LoginView: View {
    
    var body: some View {
        SignInUpWithAppleButton {
            await onSignInWithApple($0)
        }
    }
}

private extension LoginView {
    
    func onSignInWithApple(_ result: Result<SignInWithAppleResult, any Error>) async {
        
        switch result {
        case .success(let success):
            do {
                
                let credential = OAuthProvider.credential(
                    providerID: .apple,
                    idToken: tokenId,
                    rawNonce: nonce
                )
                let authInfo = try await Auth.auth().signIn(with: credential)
                ログイン後の処理
            }
            catch {
                
                handleError(error)
            }
            
        case .failure(let failure):
            handleError(failure)
        }
    }
    
    func handleError(_ error: any Error) {
        <エラー処理>
    }
}

try await Auth.auth().signIn(with: credential)の部分でFirebase側へのログインは完了します。
実際に実機やシミュレーターでアプリを動かし、Appleアカウントでサインインしてみると、Firebaseのコンソール画面にログインした情報が表示されています。
これが出ていれば、最低限のログイン処理は完了です。(ログイン情報な監視や、ログアウト処理に関してはFirebase Authentication共通なので省略します)

image.png

Sign in with Appleはシミュレーターでも確認できますが、事前にシミュレーターの設定アプリからAppleアカウントにサインインしておく必要があります。

おわり

以上がFirebase AuthenticationでSign in with Appleを利用する方法でした。
設定周りは他のプロバイダに比べればちょっと大変だなという印象でした。(あんまり多くのプロバイダ試してないので、この辺の感想はそのうち手のひら返ししそうですが)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?