Firebase で Sign In With Apple を実装する

2020年7月12日 engineering

こんにちは、 @kz_morita です。

Firebase Auth を用いて、Sign in with Apple の実装をしたのでまとめていきます。

準備

App に Sign in with Apple の Capability を追加

Apple Developer サイトと、Xcode 11 以上で Capability を追加します。

Certificates, Identifiers & Profiles > Identifiers で対象のアプリの APP ID の設定を編集して、Sign in with Apple を有効化します

詳しくは、メルカリさんのテックブログなどを参考にしてみてください。 https://tech.mercari.com/entry/2019/12/11/115331

また、Xcode 上でも Capability を追加します。

プロジェクトの設定から、Signing & Capability タブの + Capability を押して Sign in with Apple を追加します。

Firebase の Auth に Sign in with Apple を追加

Firebase の設定画面上で Sign in with Apple を有効化します。

ボタンを表示する

ボタンを表示するロジックは以下のようになります。

import AuthenticationServices

// ...
// ..
// .

private func setupAppleLoginButton() {
    guard #available(iOS 13.0, *) else {
        return
    }

    let appleLoginButton = ASAuthorizationAppleIDButton()

    // 高さや Radius などを設定
    appleLoginButton.height = BUTTON_HEIGHT
    appleLoginButton.heightAnchor.constraint(equalToConstant: BUTTON_HEIGHT).isActive = true
    appleLoginButton.cornerRadius = BUTTON_HEIGHT * 0.5

    // タップされた時の処理を実装
    appleLoginButton.addTarget(self, action: #selector(didTapAppleLogin), for: .touchUpInside)

    // View に追加
    self.buttonStackView.insertArrangedSubview(appleLoginButton, at: 0)
}

基本的には、Apple の標準のボタンを使うことになると思います。 ASAuthorizationAppleIDButton を用いて、上記の例では高さや Radius を調整しています。

Apple Developer - ASAuthorizationAppleIDButton

また、Sign in with Apple は iOS 13 以降をサポートしていますので、guard でチェックを入れています。

今回のアプリでは、Twitter 認証も用意していたので、Twitter 用のボタンと Sign in with Apple のボタンを StackView にいれて並べました。

ボタンは、自前で実装することも可能ですが、ガイドラインが定められているためデザイン上問題がない限りはデフォルトのものを使用していくのが良いかなぁと思います。

認証処理の実装

ボタンがタップされた後の認証処理は以下のような実装になります。

import AuthenticationServices

func authorize(view: UIViewController) {

    // Generate nonce
    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 = view as! ASAuthorizationControllerDelegate
    authorizationController.presentationContextProvider = view as! ASAuthorizationControllerPresentationContextProviding
    authorizationController.performRequests()
}

上記のコードは、Firebase の公式のサンプルを参考にしています。

Firebase Docs - iOS で Apple を使用して認証する

sha256randomNonceString は以下のような関数です

import CryptoKit

@available(iOS 13, *)
private func sha256(_ input: String) -> String {
    let inputData = Data(input.utf8)
    let hashedData = SHA256.hash(data: inputData)
    let hashString = hashedData.compactMap {
        return String(format: "%02x", $0)
    }.joined()

    return hashString
}

// Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
private func randomNonceString(length: Int = 32) -> String {
    precondition(length > 0)
    let charset: Array<Character> = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
    var result = ""
    var remainingLength = length

    while remainingLength > 0 {
        let randoms: [UInt8] = (0 ..< 16).map { _ in
            var random: UInt8 = 0
            let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
            if errorCode != errSecSuccess {
                fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
            }
            return random
        }

        randoms.forEach { random in
            if remainingLength == 0 {
                return
            }

            if random < charset.count {
                result.append(charset[Int(random)])
                remainingLength -= 1
            }
        }
    }

    return result
}

上記の処理を実装すると、Apple が提供する UI 上で、Apple ID の認証を行うことができます。

認証が完了すると credential が 取得できるのですが、それをうけとるのが ASAuthorizationControllerDelegate を実装したクラスになります。

authorizationController.delegate = view as! ASAuthorizationControllerDelegate

Delgate を実装したクラスは以下のようになります。

@available(iOS 13.0, *)
extension ViewController: ASAuthorizationControllerDelegate {

    /// - Tag: did_complete_authorization
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        switch authorization.credential {
        case let appleIDCredential as ASAuthorizationAppleIDCredential:

            // credential が取得できるのでそれを使って、Firebase の認証を行う
        default:
            break
        }
    }


    /// - Tag: did_complete_error
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        // Handle error.
    }
}

また、Sign in with Apple の UI を表示する window を指定する以下も実装する必要があります。

上記のコードで言う以下の部分です。

authorizationController.presentationContextProvider = view as! ASAuthorizationControllerPresentationContextProviding

実装したクラスは以下のようになります。

@available(iOS 13.0, *)
extension ViewController: ASAuthorizationControllerPresentationContextProviding {
    /// - Tag: provide_presentation_anchor
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return self.view.window!
    }
}

Firebase Auth

ここまでで、取得できた credential を用いて、Firabase の singin を行っていきます。

まず、Apple の credential から Firebase の credential を作成します. 第一引数の providerID は "apple.com" を指定します。第二引数は、Apple の Authorize で取得した Token から取得したものになります。 第三引数の rawNoncerandomNonceString で生成して、sha256 でハッシュ化する前の nonce を指定します。

let credential = OAuthProvider.credential(
    withProviderID: "apple.com",
    idToken: appleIDCredential.identityToken,
    rawNonce: rawNonce)

最後に上記で生成した、credential を使用して認証をします。

Auth.auth().signIn(with: firebaseCredential) { (authResult, error) in
  //
}

これで、Firebase と Sign in with Apple を用いた認証が実現できるかと思います。

まとめ

今回は、Sign in with Apple と Firebase を用いて認証機能を作るところをまとめました。作業として若干手間でありつつもそんなに難しくはなかったという印象です。 このあたりの作業は、アプリを作るたびにやる可能性があるもの (ドメインロジックじゃない) なので、できるだけパパッとスムーズに作れるようになれると良いなぁと思います。

この記事をシェア