猫みたいにゆる〜りと生きたいフリーランスエンジニアのブログ

.NET ゆる〜りワーク

  • Home
  • Swift・SwiftUI
  • Flutter
  • Freelance
  • Others
  • Contact
search

Swift・SwiftUI2020.07.25

Firebase Authentication の iOS 導入手順【⑧Appleアカウントログイン編】【SwiftUI】

  • ポスト
  • シェア
  • はてブ
  • 送る
Firebase Authentication 関連記事
  • ①基本設定編
  • ②メールアドレス・パスワード認証によるユーザー登録編
  • ③メールアドレス・パスワード認証によるログイン・ログアウト編
  • ④メールアドレスとパスワードの更新編
  • ⑤メールアドレスの確認メール送信・パスワード再設定メール送信編
  • ⑥ユーザーの再認証編
  • ⑦Googleアカウントログイン編
  • ⑧Apple アカウントログイン編(本記事)
  • ⑨ユーザープロフィール更新編
  • ⑩匿名ログイン編

前回の Googleアカウントログインに引き続いて、今回は、Appleアカウントログインの手順を解説します。

前提として、Appleアカウントによるログインを実装するには、Apple Developer Program に参加(有料)していなければいけません。

Apple Developer Program の登録手順は以下の記事で紹介していますので参考にしてください。

Apple Developer Program 登録手順【2020年7月時点最新】

Firebase Authentication の iOS 導入手順【⑧Appleアカウントログイン編】【SwiftUI】

(1) デベロッパーサイトで「Apple でサインイン」を有効にする

Apple デベロッパーサイトの Account ページを開き、[Certificates, Identifiers & Profiles] > [Identifier] と進み、導入したいアプリのAppIDをクリックします。

「Edit your App ID Configuration」のページが開くので、「Capabilities」の中から「Sign In with Apple」にチェックをし、[Save] > [Confirm] とクリックします。

(2) Firebase 側で Apple ログインを有効化する

次に、Firebase コンソールに移動し、Apple をログインプロバイダとして有効にします。

[Authentication] > [Sign-in method] から、「Apple」を選択し、有効にして保存します。

ステータスが有効になったか確認しましょう。

(3) Signing & Capabilities に Sign in with Apple を追加

次に Xcode プロジェクト側の設定です。

[Target] > [Signing & Capabilities] から 「+ Capability」をクリックし、開いたウィンドウで「Sign In with Apple」をダブルクリックで追加します。

開発、ステージング、本番など、Target が複数ある場合はそれぞれ追加するようにしてください。

(4) プログラム実装

基本的なプログラムは Firebase の公式ページのサンプルコードを踏襲していますが、SwiftUI で実装することを考慮した内容となっています。

1) AppleSignInButton

「Sign in with Apple」のボタンを、SwiftUI の画面で使える View として作成します。

struct AppleSignInButton: View {
    
    var completed: (() -> Void)?
    
    init(completed: (() -> Void)?) {
        self.completed = completed
    }
    
    var body: some View {
        AppleSignInButtonViewController(completed: self.completed)
    }
}

サインイン完了後に内部でコールしてもらうクロージャを渡しています。

実際の body 部分は、後述する AppleSignInButtonViewController となっており、completed を更に受け渡しています。

2) AppleSignInButtonViewController

AppleSignInButton の実体(?)部分です。

UIViewControllerRepresentable 継承し、makeUIViewController で UIViewController を生成します。

こちらでも completed を受け渡しています。

struct AppleSignInButtonViewController: UIViewControllerRepresentable {
    
    var completed: (() -> Void)?
    
    init(completed: (() -> Void)?) {
        self.completed = completed
    }
    
    func makeUIViewController(context: Context) -> UIViewController {
        let viewController = AppleSignInViewController()
        viewController.completed = self.completed
        
        return viewController
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        
    }
}

3) AppleSignInViewController

今回の中核部分になります。大きく分けて以下の4つがポイントです。

  • ASAuthorizationAppleIDButton の作成
  • 「Sign in with Apple」のフロー呼び出し(startSignInWithAppleFlow)
  • ASAuthorizationControllerPresentationContextProviding の実装
  • ASAuthorizationControllerDelegate の実装

AppleSignInViewController 全体のソースコードは以下の通りです。

import UIKit
import AuthenticationServices
import CryptoKit
import FirebaseAuth

class AppleSignInViewController: UIViewController {

    var completed: (() -> Void)?
    fileprivate var currentNonce: String?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let appleSignInButton = ASAuthorizationAppleIDButton(authorizationButtonType: .default, authorizationButtonStyle: .black)
        appleSignInButton.addTarget(self, action: #selector(appleSignInButtonTapped(sender:)), for: .touchUpInside)
        self.view.addSubview(appleSignInButton)
    }
    
    @objc func appleSignInButtonTapped(sender: Any) {
        startSignInWithAppleFlow()
    }
    
    @available(iOS 13, *)
    private 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()
    }
    
    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
    }
    
    @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
    }
}

extension AppleSignInViewController: ASAuthorizationControllerPresentationContextProviding {
    
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        guard let window = UIApplication.shared.delegate?.window else {
            fatalError()
        }
        return window!
    }
}

extension AppleSignInViewController: 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
            }
            let credential = OAuthProvider.credential(
                withProviderID: "apple.com",
                idToken: idTokenString,
                rawNonce: nonce
            )
            Auth.auth().signIn(with: credential) { (authResult, error) in
                if let error = error {
                    print(error.localizedDescription)
                    return
                }
                self.completed?()
            }
        }
    }
    
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        print(error.localizedDescription)
    }
}

部分部分を切り取って見ていきましょう。

先ず、viewDidLoad で Apple 規定のデザインボタンである ASAuthorizationAppleIDButton を作成しています。タップ時のアクション(appleSignInButtonTapped)もここで関連付けしています。

let appleSignInButton = ASAuthorizationAppleIDButton(authorizationButtonType: .default, authorizationButtonStyle: .black)
appleSignInButton.addTarget(self, action: #selector(appleSignInButtonTapped(sender:)), for: .touchUpInside)
self.view.addSubview(appleSignInButton)

「Sign in with Apple」の認証リクエストを行っているのが startSignInWithAppleFlow メソッドの内容です。

@available(iOS 13, *)
private 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()
}

ログインのたびにランダムな文字列(ナンス)をリクエストに与える必要があります。ナンスの作成には、Firebase 公式ページに紹介されているメソッドをそのまま流用しています。

ランダム文字列の生成を randomNonceString メソッドで、暗号化を sha256 メソッドで行っています。

リクエストデータを渡して ASAuthorizationController 生成します。delegate と presentationContextProvider に self(UIViewController)をセットし、performRequests でサインインフローが開始されます。

ASAuthorizationControllerPresentationContextProviding の実装では、presentationAnchor を実装しアプリケーションの UIWindow を返すようにします。

extension AppleSignInViewController: ASAuthorizationControllerPresentationContextProviding {
    
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        guard let window = UIApplication.shared.delegate?.window else {
            fatalError()
        }
        return window!
    }
}

ASAuthorizationControllerDelegate の実装では、

「Apple でサインイン」が正常に行われた際にコールされる authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) の実装と、

何らかのエラー発生時にコールされる authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) の実装を行います。

extension AppleSignInViewController: 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
            }
            let credential = OAuthProvider.credential(
                withProviderID: "apple.com",
                idToken: idTokenString,
                rawNonce: nonce
            )
            Auth.auth().signIn(with: credential) { (authResult, error) in
                if let error = error {
                    print(error.localizedDescription)
                    return
                }
                self.completed?()
            }
        }
    }
    
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        print(error.localizedDescription)
    }
}

didCompleteWithAuthorization では、ASAuthorizationAppleIDCredential で Apple の認証情報を取り出します。

サインインをリクエストした際に保存しておいたナンスと共に、Firebase のサインイン用の認証情報(credential)を OAuthProvider.credential で作成し、Auth.auth().signIn で Firebase にサインインします。

サインアウトする際は Auth.auth().signOut をコールすればOKです(サインアウトはプロバイダ種別を問いません)。

4) SwiftUI での実装サンプル

最後に、SwiftUI の View に Sign in with Apple のボタンを設置したサンプルを紹介します。

struct ContentView: View {
    
    @State private var showSignedIn = false
    
    var body: some View {
        VStack {
            AppleSignInButton(completed: {
                self.showSignedIn = true
            })
            .alert(isPresented: $showSignedIn) {
                Alert(title: Text("Sign in with Apple."), message: Text("サインインしました"), dismissButton: .default(Text("OK")))
            }
        }
        
    }
}

不格好ですが、左上にサインインボタンがあります。

ボタンをタップし、正常にリクエストされると以下のような画面が表示されるはずです。

Continue で進み、パスワードを入力して認証されるとアプリ側に処理が戻ります。無事 Firebase のサインインまで完了するとダイアログが表示されるはずです。

以上が「Sign in with Apple」の導入手順となります。

他、メールアドレスとパスワードによるサインアップ・サインイン、Google アカウントによるサインインについての記事もありますのでご参考ください。

Firebase Authentication の iOS 導入手順【②メールアドレス・パスワード認証によるユーザー登録編】【SwiftUI】
Firebase Authentication の iOS 導入手順【③メールアドレス・パスワード認証によるログイン・ログアウト編】【SwiftUI】
Firebase Authentication の iOS 導入手順【⑦Googleアカウントログイン編】【SwiftUI】

  • Swift・SwiftUI
  • Firebase
  • Sign in with Apple
  • SwiftUI
  • ポスト
  • シェア
  • はてブ
  • 送る

コメントを残す コメントをキャンセル

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です

【Firebase・iOS】GoogleService-Info.plist を環境別に読み込む方法
Apple Developer Program 登録手順【2020年7月時点最新】
RECOMMEND
  • Swift・SwiftUI
    2020.10.04
    【SwiftUI】EnvironmentObjectを使って先頭のViewに戻る方法【画面遷移】
  • Swift・SwiftUI
    2020.04.16
    【SwiftUI】Font と Font.Weight まとめ
  • Swift・SwiftUI
    2020.03.20
    VIPERアーキテクチャーのサンプル紹介
  • Swift・SwiftUI
    2021.11.07
    【SwiftUI 3.0】toggleStyle(.button) でボタン風スイッチを実装
  • Swift・SwiftUI
    2020.04.13
    【SwiftUI】戻るボタンを使わずにコードで前の画面へ戻る方法(MVVMバージョン)
  • Swift・SwiftUI
    2020.04.23
    【SwiftUI】Form を使って設定アプリもどきの画面を作成する
  • Swift・SwiftUI
    2021.04.29
    【SwiftUI】ネスト(階層化)した List を表示する
  • Swift・SwiftUI
    2020.10.18
    【SwiftUI】フルスクリーンでモーダル表示する【fullScreenCover】

Profile

佐藤 亮介

酒と子どもと時々奥さんをこよなく愛するフリーランスエンジニアです。

専門はiOSアプリ開発で、最近は SwiftUI や Flutter に注力しています。

ちょっとお金に強いエンジニアを目指してます(FP3級保持)

個人事業主兼法人(yururiwork合同会社)代表

twitter:@freeenginyaaa

Menu

  • Home
  • Swift・SwiftUI
  • Flutter
  • Freelance
  • Others
  • Contact

最近の投稿

  • 【Flutter】Module was compiled with an incompatible version of Kotlin. の解消方法
  • 【Android Studio】Git is not installed のエラーが出た時の対処
  • 【GitHub】「WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!」とエラーが出た時の対処
  • 【Flutter】FilledButton と OutlinedButton
  • 【Flutter】パスワード入力用のTextFieldを作る

最近のコメント

  • Flutter プロジェクトの作成とiOS・Androidの実行まで【開発環境構築②】 に 【Flutter】開発環境構築時のトラブルシューティングまとめ - WEB制作・開発者の記録 より
  • フリーランスエンジニアは素直に個人事業税を納めてはいけない件 に 個人事業主のシステムエンジニアが税務署から個人事業税についての質問が来た際にどう答えるか – テリア より
  • Xcode12にアップデートしたらPodライブラリで大量に警告が発生したので対処した件 に 【Xcode14.2】macOS を Monterey から Ventura にアップデートした時にやったことメ | Cookin より
  • SwiftUI で WebView を表示する に Arangott Ramesh より
  • Xcode12にアップデートしたらPodライブラリで大量に警告が発生したので対処した件 に iOSターゲット条件の設定を変更する | sunny man より

アーカイブ

  • 2023年4月
  • 2023年2月
  • 2023年1月
  • 2022年10月
  • 2022年9月
  • 2022年8月
  • 2022年7月
  • 2022年4月
  • 2022年3月
  • 2022年1月
  • 2021年12月
  • 2021年11月
  • 2021年10月
  • 2021年9月
  • 2021年8月
  • 2021年7月
  • 2021年6月
  • 2021年5月
  • 2021年4月
  • 2021年3月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年10月
  • 2020年9月
  • 2020年8月
  • 2020年7月
  • 2020年6月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年12月
  • 2019年11月
  • 2019年9月

カテゴリー

  • Flutter
  • Freelance
  • Others
  • Swift・SwiftUI
  • HOME
  • Swift・SwiftUI
  • Firebase Authentication の iOS 導入手順【⑧Appleアカウントログイン編】【SwiftUI】

© 2025 .NET ゆる〜りワーク All Rights Reserved.