【SwifUI】Sign In With Appleの機能をSwiftUIで実装する
概要
アップル独自の認証方法「Sign in with Apple」について、SwiftUIを使って実装してみます。
環境
- XCode: Version 11.3.1
- Swift: Version 5.1
Sign in with appleのボタンの作成
まず、はじめにボタンコンポーネントの作成から実装します。
Sign in with appleに使用するボタンについては、Apple側で ASAuthorizationAppleIDButton
という標準のボタンコンポーネントを用意してくれています。
これを使うとわざわざデザインしなくても公式のボタンデザインが使用できるので通常はこのコンポーネントを使うのがいいのではないかと思います。
(認証機能自体は別実装ですので独自デザインも可能です)
ただし、ASAuthorizationAppleIDButtonは、SwiftUIの標準コンポーネントとしては提供されていません。
そのため 、まずはこのボタンをSwiftUIのViewコンポーネントとして使えるようにします。
import SwiftUI import AuthenticationServices struct SignInWithAppleButton: UIViewRepresentable { func makeUIView(context: Context) -> ASAuthorizationAppleIDButton { let button = ASAuthorizationAppleIDButton() return button } func updateUIView(_: ASAuthorizationAppleIDButton, context _: Context) {} }
UIKitで用意されているUIコンポーネントをSwiftUIのViewコンポーネントに橋渡しするためには、UIViewRepresentable
を適用したstructを用意します。
今回は、UIViewRepresentableについての詳細は割愛しますが、このプロトコルの makeUIView
というメソッドを実装し、表示したいUIコンポーネントを返すと、SwiftUI側でViewコンポーネントとして使用できるようになります。
またASAuthorizationAppleIDButton
を使用するためには、 AuthenticationServices
というパッケージが必要のためimportしています。
ボタンの準備はこれだけでOKです。あとはこのボタンをSwiftUIの画面に配置すればSign in With Appleのボタンを表示することができます。
import SwiftUI struct ContentView: View { var body: some View { VStack { SignInWithAppleButton() .frame(width: 200.0, height: 60.0) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
ボタンには、サイズ指定をしていないため frame
Modifierを使ってサイズを指定しています。
ASAuthorizationAppleIDButton には、ボタンの種別(authorizationButtonType
)とボタンのスタイル(authorizationButtonStyle
)の2つのプロパティを設定することができます。
authorizationButtonTypeには、
- continue
- signUp
- signIn
authorizationButtonStyleには、
- black
- whiteOutline
- white
が指定でき、用途やアプリのデザイン別にボタンを出し分けることも可能です。
ASAuthorizationAppleIDButton(authorizationButtonType: .signUp, authorizationButtonStyle: .whiteOutline)
authorizationButtonTypeを signUp
、authorizationButtonStyleを whiteOutline
に設定するとこのようになります。
認証処理の実装
ボタンの見た目を作ることができたので、ここからは認証処理を実装していきます。
Sign In with Appleの認証処理を行うにはASAuthorizationAppleIDProviderとASAuthorizationControllerを使用します。
@objc func didTapButton() { let appleIDProvider = ASAuthorizationAppleIDProvider() let request = appleIDProvider.createRequest() request.requestedScopes = [.fullName, .email] let authorizationController = ASAuthorizationController(authorizationRequests: [request]) authorizationController.delegate = self authorizationController.presentationContextProvider = self authorizationController.performRequests() }
上記がリクエストの処理のサンプルです。
ASAuthorizationAppleIDProvider を使って認証リクエストを作成して ASAuthorizationController で認証を実行するという流れになります。
この処理を先程作成した「SignInWithAppleButton」に実装します。
プロジェクトの設定
実装に入る前にプロジェクトの設定をしておきましょう。
Sign In with Appleを利用するには「Target -> Signing&Capabilities -> + Capability」から Sign In with Apple
を選択して追加しておく必要があります。
※ この設定を追加しておかないと、実行時にエラーが発生します。
実装
次に、「SignInWithAppleButton」に認証処理を追加します。
UIViewRepresentableを適合したコンポーネントは、Coordinator
というクラスを使ってコンポーネント上の処理を実装することができます。
今回は、このCoordinatorクラスを使って認証処理を実装します。
import SwiftUI import AuthenticationServices struct SignInWithAppleButton: UIViewRepresentable { func makeUIView(context: Context) -> ASAuthorizationAppleIDButton { let button = ASAuthorizationAppleIDButton() // NOTE: ボタン押下時のイベント処理を追加 // CoordinatorクラスのdidTapButtonメソッドでイベントを受け取る button.addTarget(context.coordinator, action: #selector(Coordinator.didTapButton), for: .touchUpInside) return button } func updateUIView(_: ASAuthorizationAppleIDButton, context _: Context) {} func makeCoordinator() -> Coordinator { // NOTE: Coordinatorを作成する処理 // 初期値にView自身を渡す Coordinator(self) } } final class Coordinator: NSObject { var parent: SignInWithAppleButton init(_ parent: SignInWithAppleButton) { self.parent = parent super.init() } // ボタンコンポーネントをタップされたときの処理 @objc func didTapButton() { // NOTE: リクエストの作成 let appleIDProvider = ASAuthorizationAppleIDProvider() let request = appleIDProvider.createRequest() // NOTE: 認証情報として、「ユーザ名」と「メールアドレス」を受け取る request.requestedScopes = [.fullName, .email] // NOTE: 認証リクエストの実行 let authorizationController = ASAuthorizationController(authorizationRequests: [request]) authorizationController.delegate = self authorizationController.presentationContextProvider = self authorizationController.performRequests() } } extension Coordinator: ASAuthorizationControllerDelegate { // 認証処理が完了した時のコールバック func authorizationController(controller _: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential { print("認証成功", appleIDCredential) } } // 認証処理が失敗したときのコールバック // NOTE: 認証画面でキャンセルボタンを押下されたときにも呼ばれる func authorizationController(controller _: ASAuthorizationController, didCompleteWithError error: Error) { print("認証エラー", error) } } extension Coordinator: ASAuthorizationControllerPresentationContextProviding { // 認証プロセス(認証ダイアログ)を表示するためのUIWindowを返すためのコールバック func presentationAnchor(for _: ASAuthorizationController) -> ASPresentationAnchor { let vc = UIApplication.shared.windows.last?.rootViewController return (vc?.view.window!)! } }
ボタンを押下されたときのイベントを Coodinator
クラスで受け取って(didTapButton)認証リクエストを実行します。
今回は、認証情報として「ユーザ名(.fullName
)」と「メールアドレス(.email
)」を取得するようにScopeを設定しています。
Coordinator クラスでは、認証処理のdelegate(ASAuthorizationControllerDelegate
)と認証プロセスを提供するためのUIWindowを指定するコールバック(ASAuthorizationControllerPresentationContextProviding
)を実装する必要があります。
これで実装は完了です。
動作確認
アプリを起動して、ボタンを押下すると上記のようなダイアログが表示され認証処理が開始されます。
正しく認証が行われると結果をdelegateで受け取れます。
あとは、コールバック処理を「SignInWithAppleButton」に追加するなど対応すれば、SwiftUI上で認証結果を受け取れるようになります。
注意事項
個人的に気になったことがあったので記載しておきます。
① 認証ダイアログで「キャンセル」ボタンを押下されたときの挙動
認証ダイアログで「キャンセル」ボタンを押下されると、現状の挙動だと ASAuthorizationControllerDelegate
のコールバックである authorizationController(controller _: ASAuthorizationController, didCompleteWithError error: Error)
が呼ばれるようです。
個人的にはエラーとキャンセルで処理を分けたかったのですが、今はおそらく同じ箇所に入るためエラーコードで判断するなどの必要があるかもしれません。
② 認証の付加情報は2回目以降は、返ってこない(nilになる)
authorizationController(controller _: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization)
では認証成功の結果として credential情報が受け取れるのですが、requestScopesで指定した付加情報は2回目以降の認証では入っていません(nilになっています)。
(ID情報であるUserIdは常に返却されます)
そのため、この情報をサーバなどで処理する場合は、初回の認証時にどこかアプリ側で保存しておく必要があります。
この認証履歴は、アプリ内で保有されるものではないためアプリをアンインストールしてもリセットされないため注意が必要です。
(ユーザ自身が「設定 -> パスワードとセキュリティ -> Apple IDを使用中のApp」から対象のアプリの認証情報を削除するとリセットされます)
初回で取れた情報は、キーチェーンに保存するなどしてリカバリ方法を検討する必要があるかもしれません。
ソースコード
所感
実装は、思ったよりも大分シンブルでした。
他サービス認証(Facebook, Google)などと比較して、SDKを入れる必要もないことから実装コストは大分小さい印象です。
またユーザ観点からも、Apple認証を使うほうがセキュリティ的な安心感もあるため、今後は少なくともiOSアプリについてはSign In with Appleが主流になってくるのかなと思っています。