iOS
Swift
iOS13

【iOS】対応必須かも?Sign In with Appleまとめ(第一報)


Sign In with Appleとは?

今年のWWDCで発表された

Appleアカウントでアプリの

サインアップやサインインができる機能です。

スクリーンショット 2019-06-07 8.38.08.png


対応必須?

様々なニュースで下記のようなことが書かれており

https://japan.cnet.com/article/35138002/

レビューガイドラインを見る限り

サードパーティーのログイン連携をしているアプリは

Sign In with Appleの実装が必須になるようです。

弊社のアプリでもFacebookログインなどを使用しており

今後対応が必要になるだろうと思い

まずどういうものか知るために

サンプルコードとセッション動画の内容から

現状わかったことをまとめてみました。

公式のサイトはこちら

https://developer.apple.com/sign-in-with-apple/

レビューガイドラインの更新情報はこちら

https://developer.apple.com/news/?id=06032019j

Introducing Sign In with Apple(セッション動画)

https://developer.apple.com/videos/play/wwdc2019/706/

サンプルコード

https://developer.apple.com/documentation/authenticationservices/adding_the_sign_in_with_apple_flow_to_your_app

ちなみに

ベータテストは今夏に

正式版は今年の終わり近くに利用可能になる予定のようです。

Sign In with Apple will be available for beta testing this summer. 

It will be required as an option for users in apps that
support third-party sign-in when it is commercially available later this year.

※ 

Xcodeの画面などはNDAの関係があるため

正式版になったあとに追加させていただきます:bow_tone1:

使用しているものは

公開されているスライド、ドキュメントやサンプルコードを使用していますが

もし不適切な箇所がございましたら

ご指摘いただけますと幸いです:bow_tone1:


Sign In with Appleの特徴

スクリーンショット 2019-06-07 5.48.55.png

早く、簡単にアプリへの

サインアップやサインインができるとのことです。

主な特徴として下記の5つの点が挙げられていました。

スクリーンショット 2019-06-07 5.47.37.png


合理化されたアカウントセットアップ

すでにAppleのアカウントへは

ログイン状態にあることが多いので

タップだけでセットアップが完了できます。

また

デバイスをまたがって同じ認証情報が使用可能です。


認証されたメールアドレス

Appleがすでに認証しているので

メールを受け取って->リンクをタップする

などの余計な認証プロセスが必要になることはありません。


Hide My Email

スクリーンショット 2019-06-07 9.07.11.png

スクリーンショット 2019-06-07 9.06.00.png

実際のメールアドレスとは異なる

下記のようなメールアドレスを使うことができ

アプリに個人情報を渡すことを避けることができます。

もちろん本物のメールアドレスと同じように使えます。

※ Appleがメール内容を保持するということはないとも言っていました。

スクリーンショット 2019-06-07 8.38.27.png


ビルトインのセキュリティ

パスワードを忘れたとしても

Appleアカウントでは2段階認証を使っているため

追加のパスワード認証プロセスや入力が必要ありません。


Anti-Fraud(詐欺対策)

ユーザが疑わしいかどうかをシステムがサポートしてくれます。

内部的な情報とアカウント情報を照らし合わせて判断しているようです。

スクリーンショット 2019-06-07 8.38.37.png


マルチプラットフォーム

iOS macOS iPadOS watchOS Javascript

実装することが可能です。


処理の流れ

下記の様な手順で行っていきます。

スクリーンショット 2019-06-07 5.47.59.png

具体的にどのような処理を行っていくか

ここからはサンプルのコードで見ていきたいと思います。

Sign in with Appleを使用するためには

AuthenticationServicesというフレームワークを用います。

https://developer.apple.com/documentation/authenticationservices

これを使用することで


  • AppleIDを用いてログインをすることができる

  • サインインフローの中で保存しているパスワードを使用することができる

  • OAuthのような仕組みを使用してWebブラウザとアプリ間でデータを共有することができる

  • エンタープライズアプリではシングルサインオン(SSO)のような体験を提供できる

などが実現できます。

OAuthとは?

https://ja.wikipedia.org/wiki/OAuth

シングルサインオンとは?

https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%B3%E3%82%B0%E3%83%AB%E3%82%B5%E3%82%A4%E3%83%B3%E3%82%AA%E3%83%B3

では実際の中身に関してはサンプルのコードから見ていきたいと思います。


Xcodeの設定をする(Sign In with Apple Entitlementを用意する)

まずはXcodeの設定が必要です。

targetを選択しSigning&Capabilitiesの中から

Sing in With Appleを選択します。

https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_applesignin


サインアップボタンを用意する

ASAuthorizationAppleIDButtonを使用します。

標準のものをそのまま使用することも可能です。

カスタマイズも可能ですが、

その際にはデザインガイドラインに従う必要があります。

デザインガイドラインはこちら

https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/

ASAuthorizationAppleIDButton

https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidbutton


Appleにユーザ情報を取得するためのリクエストを送る

次にユーザの情報を取得するために

Appleへリクエストを送ります。

ユーザにタップされた際の処理は下記のようになります。


@objc
func handleAuthorizationAppleIDButtonPress() {
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

ASAuthorizationAppleIDProvider

ASAuthorizationAppleIDRequestを用いて

ユーザのAppleIDを元に

アプリが認証済みかどうかのリクエストを作成します。


let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]

fullNameemailは取得したい情報を指定することができ

オプションで指定することができます。

ASAuthorizationAppleIDProvider

https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovider

ASAuthorizationAppleIDRequest

https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidrequest


ASAuthorizationController

次に上記で作成したリクエストを使って

ASAuthorizationControllerを初期化し

performRequestsでAppleへリクエストを送ります。


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

ASAuthorizationController

https://developer.apple.com/documentation/authenticationservices/asauthorizationcontroller

performRequests

https://developer.apple.com/documentation/authenticationservices/asauthorizationcontroller/3153047-performrequests


ASAuthorizationControllerDelegate

上記の中で


authorizationController.delegate = self

とありますが

これはASAuthorizationControllerDelegateというプロトコルです。

ASAuthorizationControllerDelegate

https://developer.apple.com/documentation/authenticationservices/asauthorizationcontrollerdelegate

これを設定するとリクエストのレスポンスを下記のデリゲートで取得することができます。


extension LoginViewController: ASAuthorizationControllerDelegate {
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {

// ASAuthorizationAppleIDCredentialの場合
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {

// 取得できた情報
let userIdentifier = appleIDCredential.user
let fullName = appleIDCredential.fullName
let email = appleIDCredential.email

// 取得した情報を元にアカウントの作成などを行う

// ASPasswordCredentialの場合(※あとで紹介します※)
} else if let passwordCredential = authorization.credential as? ASPasswordCredential {
// 既存のiCloud Keychainクレデンシャル情報
let username = passwordCredential.user
let password = passwordCredential.password

// 取得した情報を元にアカウントの作成などを行う

}
}
}

ASAuthorizationAppleIDProviderのリクエストが成功した場合

取得できる情報はASAuthorizationAppleIDCredentialです。

ASAuthorizationAppleIDCredential

https://developer.apple.com/documentation/authenticationservices/asauthorization

この中の情報を使ってアカウントの作成などを行います。

authorizationController(controller:didCompleteWithAuthorization:)

https://developer.apple.com/documentation/authenticationservices/asauthorizationcontrollerdelegate/3153050-authorizationcontroller

エラーの場合は下記のデリゲートメソッドが呼ばれます。


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

authorizationController(controller:didCompleteWithError:)

https://developer.apple.com/documentation/authenticationservices/asauthorizationcontrollerdelegate/3153051-authorizationcontroller


ASPasswordCredentialについて

上記のデリゲートメソッドの中で


} else if let passwordCredential = authorization.credential as? ASPasswordCredential {

という分岐が出てきていますが

これはiCloud KeyChainのパスワード情報をリクエストした場合に

取得できる情報です。

ASPasswordCredential

https://developer.apple.com/documentation/authenticationservices/aspasswordcredential

実はサンプルコードの中では

ユーザのタップする場合以外にも

viewDidAppearの中でも

認証情報のリクエストを送っています。


func performExistingAccountSetupFlows() {
// Prepare requests for both Apple ID and password providers.
let requests = [ASAuthorizationAppleIDProvider().createRequest(),
ASAuthorizationPasswordProvider().createRequest()]

// Create an authorization controller with the given requests.
let authorizationController = ASAuthorizationController(authorizationRequests: requests)
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}

注目点は下記で


let requests = [ASAuthorizationAppleIDProvider().createRequest(),
ASAuthorizationPasswordProvider().createRequest()]

ASAuthorizationPasswordProviderを使っています。

ASAuthorizationPasswordProvider

https://developer.apple.com/documentation/authenticationservices/asauthorizationpasswordprovider

このリクエストを送ることで

iCloud Keychainのクレデンシャル情報を使って

アカウントの作成などが可能になります。

※複数設定した場合の優先順位などに関しては何も言及がありませんでしたので

これは今後調べたいと思います。

今回は割愛させていただきますが

シングルサインオン用の入り口もあります。

ASAuthorizationSingleSignOnProvider

https://developer.apple.com/documentation/authenticationservices/asauthorizationsinglesignonprovider

ASAuthorizationSingleSignOnCredential

https://developer.apple.com/documentation/authenticationservices/asauthorizationsinglesignoncredential


ASAuthorizationControllerPresentationContextProviding

リクエストを送る際に下記のコードがありました。


authorizationController.presentationContextProvider = self

これはASAuthorizationControllerPresentationContextProvidingプロトコルで

システムが認証プロセスをユーザに提供するためのUIWindowの指定をします。

いくつかのリクエストでは必須となっているようです。

ASAuthorizationControllerPresentationContextProviding

https://developer.apple.com/documentation/authenticationservices/asauthorizationcontrollerpresentationcontextproviding

サンプルコードでは

下記のようにアプリのUIWindowを設定しています。


extension LoginViewController: ASAuthorizationControllerPresentationContextProviding {
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return self.view.window!
}
}

presentationAnchor(for:)

https://developer.apple.com/documentation/authenticationservices/asauthorizationcontrollerpresentationcontextproviding/3237228-presentationanchor

このようにしてユーザのアカウント作成ができます。


クレデンシャル情報の変更に対応する

例えば


  • アプリでAppleIDの使用をやめた

  • デバイスでサインアウトをした

などが起きた場合への対応も必要になります。

サンプルのAppDelegateを見ていきます。


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let appleIDProvider = ASAuthorizationAppleIDProvider()
appleIDProvider.getCredentialState(forUserID: KeychainItem.currentUserIdentifier) { (credentialState, error) in
switch credentialState {
case .authorized:
// Apple IDクレデンシャルが妥当だった場合
break
case .revoked:
// Apple IDクレデンシャルが無効だった場合
break
case .notFound:
// Apple IDクレデンシャル情報がなかった場合

// サインアップのフローを開始する

default:
break
}
}
return true
}

発表のスライドとはやや異なりますが、

Keychainに保存しているcurrentUserIdentifier

getCredentialState(forUserID:completion:)の引数として設定し

クレデンシャル情報を取得しています。

getCredentialState(forUserID:completion:)

https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovider/3175423-getcredentialstate

ここで得られるcredentialState

ASAuthorizationAppleIDProvider.CredentialStateです。


enum CredentialState : Int {
case authorized = 1
case notFound = 2
case revoked = 3
}

ASAuthorizationAppleIDProvider.CredentialState

https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidprovider/credentialstate

このrevokedの際に対応していきます。

また発表スライドでは

通知を受け取ることもできるとあり

下記のような形で

クレデンシャル情報が無効になった時の処理ができます。


// Register for revocation notification
let center = NotificationCenter.default
let name = NSNotification.Name.ASAuthorizationAppleIDProviderCredentialRevoked
let observer = center.addObserver(forName: name, object: nil, queue: nil) { (Notification) in
// サインアウトして、再度サインインフローを表示するなど
}

ASAuthorizationAppleIDProviderCredentialRevoked

https://developer.apple.com/documentation/foundation/nsnotification/name/3175424-asauthorizationappleidprovidercr


サインイン後はワンタップでサインインできる

サインアップのプロセスを見てきましたが

Appleアカウントでアプリの認証がされていることで

アプリ起動時に認証されているかどうかがわかり

即座にサインイン状態だと判定することが可能です。

※ 上記AppDelegateのメソッド内の分岐をご参照ください。

これはユーザにとっても

不要な手順が省かれ楽になり

個人情報を入力するリスクを避けられます。


最後に

サンプルコードとセッションの情報から

まずまとめてみました。

まだまだ理解ができていない部分や知らないことは

多々あると思いますので

更新情報がありましたら追記したいと思います。