LoginSignup
1
2

More than 3 years have passed since last update.

【iOS】AppleサインインからFirebaseアカウントを作成する

Last updated at Posted at 2021-04-04

始めに

本ドキュメントは次の2点を目標とします。

  • iOSアプリにAppleサインインを実装する
  • Appleサインイン情報からFirebaseアカウントを作成する

執筆時の開発環境は次の通りです。

  • Xcode 12.4
  • Swift 5

Xcodeプロジェクトとそれに対応するFirebaseプロジェクトが作成済みであり、iOS側のFirebase初期導入手順は実装済みである事が前提です。
また次のライブラリが必要です。

  • FirebaseAuth

基本的にはFirebaseドキュメント通りの内容ですが、次の2点が異なります。

  • 必要最小限の情報のみ記載
  • ビルドエラーを解決したコード

実装

Firebaseの設定

Authentication画面を表示します。
Sign-in methodタブを選択し、Appleを有効化して保存します。
その他の項目を編集する必要はありません。
Firebase Authentication

iOSアプリの実装

次の流れを実装します。

  1. Appleサインインし、Apple認証情報を取得する
  2. 取得したApple認証情報をFirebaseに渡し、Firebaseアカウントを作成する

Capabilities設定

XcodeのSetting & Capabilities画面を表示し、「+Capability」をクリックします。
表示された一覧の中からSign in with Appleを追加します。
Capabilities設定

AccountManagerクラスの実装

新しくAccountManagerという名前のシングルトンクラスを実装します。
このクラスにサインインに必要な処理を実装していきます。

AccountManager.swift
class AccountManager: NSObject {

    static var shared = AccountManager()

    private override init() {
        super.init()
    }
}

Appleサインイン機能

ナンス生成関数

Appleサインインする際、得られたトークンの正当性を検証する為「ナンス」と呼ばれる文字列を使用します。

AccountManager.swift
private extension AccountManager {
    func randomNonceString(length: Int = 32) -> String {
        precondition(length > 0)

        let charset: Array<Character> = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
        var result = ""
        var remainingLength = length

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

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

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

        return result
    }
}
文字列のSHA-256暗号化関数

ナンスはSHA-256という規格で暗号化し、Appleに渡します。
String型を拡張し、文字列の暗号化関数を実装します。

String+Extension.swift
import CryptoKit

extension String {

    func sha256() -> String {
        let data = Data(self.utf8)
        let hashedData = SHA256.hash(data: data)
        let result = hashedData.compactMap {
            String(format: "%02x", $0)
        }.joined()

        return result
    }
}
Appleサインイン関数

実装するクラスに次のプロパティを追加します。

AccountManager.swift
class AccountManager: NSObject {
    // 生成したナンスを保持し、検証に使用します
    var currentNonce = ""
}

Appleサインインを行う関数を実装します。

AccountManager.swift
import AuthenticationServices

class AccountManager: NSObject {
    func requestSignInAppleFlow() {
        currentNonce = randomNonceString()

        let request = ASAuthorizationAppleIDProvider().createRequest()
        request.requestedScopes = [.fullName, .email]
        request.nonce = currentNonce.sha256()

        let controller = ASAuthorizationController(authorizationRequests: [request])
        controller.delegate = self
        controller.presentationContextProvider = self
        controller.performRequests()
    }
}

request.nonceにSHA-256で暗号化したナンスを設定しています。

ASAuthorizationControllerPresentationContextProviding

Apple認証画面を表示するwindowを指定します。
ここでは最前面のwindowを返しています。

AccountManager.swift
extension AccountManager: ASAuthorizationControllerPresentationContextProviding {
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return UIApplication.shared.windows.first { $0.isKeyWindow }!
    }
}
Firebaseサインイン処理

Appleサインインの結果はASAuthorizationControllerDelegateを通して通知されます。
Appleサインインが正しく終了した場合、その情報を使ってFirebaseのアカウントを作成します。

AccountManager.swift
import Firebase

extension AccountManager: ASAuthorizationControllerDelegate {

    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        debugPrint("Appleサインイン完了")

        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            guard let appleIDToken = appleIDCredential.identityToken else {
                debugPrint("有効なトークンが得られなかった為、処理を中断")
                return
            }

            guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
                debugPrint("トークンデータの文字列化に失敗: \(appleIDToken.debugDescription)")
                return
            }

            if currentNonce.count == 0 { fatalError("不正なNonce") }
            let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: currentNonce)

            Auth.auth().signIn(with: credential) { [unowned self] (result, error) in
                if let error = error {
                    debugPrint("Firebaseサインインに失敗: \(error.localizedDescription)")
                } else {
                    debugPrint("Firebaseサインイン完了")
                }
            }
        }
    }

    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        debugPrint("Appleサインイン失敗: \(error.localizedDescription)")
    }
}

OAuthProvider.credential()に保持しているナンスを渡し、認証情報を得ます。
Auth.auth().signIn()はFirebaseへのサインイン処理です。ここでAppleの認証情報を渡しています。

サインイン画面の実装

ViewControllerにAppleサインインボタンを配置し、タップ時の処理を実装します。

ViewController.swift
import UIKit
import AuthenticationServices

class ViewController: UIViewController {
    private let loginButton: ASAuthorizationAppleIDButton = {
        let button = ASAuthorizationAppleIDButton(authorizationButtonType: .default, authorizationButtonStyle: .whiteOutline)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(loginButton)
        loginButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        loginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true

        loginButton.addAction(.init(handler: { (_) in
            AccountManager.shared.requestSignInAppleFlow()
        }), for: .touchUpInside)
    }
}

実行

iOSアプリを実行し、サインインボタンをタップします。
Firebaseサインインが行われたログが出力されれば問題無く実装できています。
Firebaseコンソールでアカウントが作られている事が確認できます。
アカウント確認

蛇足

AccountManagerに次のプロパティを追加すると便利です。

AccountManager.swift
import Firebase

class AccountManager: NSObject {
    // Firebaseのサインイン状況
    var isSignedIn: Bool { Auth.auth().currentUser != nil }

    // FirebaseのUserID
    var uid: String? { Auth.auth().currentUser?.uid }
}
1
2
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
1
2