0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

iOS12以下でSign in with Appleを実装する

Last updated at Posted at 2021-08-16

概要

アプリが、サードパーティのログインサービスを利用してログインを実装する場合、Appleでサインイン(Sign in with Apple)の実装が必須になりました。

通常はAuthenticationServicesに含まれているAPIでSign in with Appleの処理が可能ですが、Sign in with Apple関連のAPIはiOS 13以上の対応になるので、iOS 12では別な手段を講じなければいけません。
その手段について説明します。
他プラットフォームでも一部は共通する手段になるかと思います。

必要なもの

以下の実装はUniversal Links対応されていることを想定しています。

App Developer Programで必要な作業を行う

Service IDの追加

以下の手順を参考に、Services IDsの追加を行います。
App Developer Programの構成は更新される可能性があるので参考程度にしてください。

  • identifierを選択し、+ボタンを選択
  • Services IDsをチェックし、continue
    • Description、Identifierを入力して、continue
      • Descriptionは任意の値
      • Identifierは、一意なIDにする(例:com.example)
    • 先ほど作成したService IDを選ぶ
    • Sign In with Appleにチェックを入れてConfigure
    • Web Authentication Configurationを入力
      • Primary App IDに、利用するアプリを選択
      • Domains and Subdomainsは、Universal Linksに設定しているドメインを指定 (例:example.com)
      • Return URLsに、Universal Linksが起動してアプリが立ち上がるURLを指定 (例:https://example.com/callback)
        • このコールバックURLは、それぞれのプラットフォームの起動に使う想定です

サインインするためのボタン画像を用意

サインインするための公式のボタンは、iOS 13以上であれば ASAuthorizationAppleIDButton などを使えばいいですが、iOS 12以下の場合はどちらかで画像を取得する必要があります。

ボタンを押して、Webブラウザ経由でサインインを行う

ボタンを押したときに、iOS 13であればアプリ上でSign in with Appleを行いますが、iOS 12以下の場合、Web上でのサインインが必要になります。
サインインを行うには、 https://appleid.apple.com/auth/authorize に対して各種パラメータをクエリとしてつけて、そのURLをUIApplication.shared.openで外部ブラウザで開くようにします。

var components = URLComponents(string: "https://appleid.apple.com/auth/authorize")
components?.queryItems = [
    URLQueryItem(name: "client_id", value: "com.example"),// Service IDsで作成したIdentifier
    URLQueryItem(name: "response_type", value: "id_token"),// code or id_token
    URLQueryItem(name: "redirect_uri", value: "https://example.com/callback"),// Return URLsの値
    URLQueryItem(name: "state", value: state), // 後述
    URLQueryItem(name: "nonce", value: nonce) // 後述
]
if let url = components?.url {
    UIApplication.shared.open(url, options: [:], completionHandler: nil)
}

パラメータについての詳しい説明はこちら

これで、外部ブラウザ(iOS 12の場合はSafari)が開き、Appleのサインイン画面が開きます。

アプリ内WebViewを使わない理由

アプリ内WebViewを使わず、Safariを使う理由として、アプリ内WebViewは開発者側が悪意を持っていれば情報を盗むことが可能だからです。
過去にはApple IDを盗んで、その開発者のアプリを譲渡させて自分のものにしてしまう事件も起きました。
このようなことをアプリがやっていない証明として、Safariへ一度認証用のサイトを表示させ、コールバック経由のUniversal Linksからアプリを開かせるようにします。
アプリがアプリ内WebViewを使って認証を行っているかどうかは利用者としても気をつけるべきところです。

詳しい話はこちら。
https://reliphone.jp/notes4/

stateについて

stateは、アプリでサインインボタンを押した人と、SafariでAppleのサインイン画面でサインインした人が一致するかを確かめるために使います。
stateのチェックがないと、攻撃者がコールバックURLをユーザーに送りつけて、ユーザーはそれをクリックして攻撃者としてサインインしてしまい、アプリ内で購入した通貨や商品が攻撃者のアカウントに渡ることになります。
それを防ぐためのものになります。
stateは UUID().uuidString などで生成するのがいいと思います。

nonceについて

サーバーとの連携の場合、Sign in with Appleでサインインした時のid_tokenをサーバーへ送信してサーバーが処理を行うケースが考えられますが、このid_tokenを再度送った場合、全く同じようにサーバーが処理をしてしまう可能性があります。
生成したnonceをSign in with Apple時にパラメータに渡すと、返されるid_tokenにnonceが付与されます。
これにより、一度処理をしたことがあるid_tokenの場合は処理を行わない、という対応が可能になります。

nonceの生成パターンとして、サーバー側が生成するかアプリ側が生成するかを選択できます。

  • サーバー側がnonceを生成し、アプリがサーバーからnonceを取得しSign in with Appleを行い、id_tokenをサーバーに渡す。サーバーはid_tokenをチェックして処理を行う。
  • アプリがnonceを生成し、ハッシュ化したnonceをSign in with Appleに渡す。id_tokenが渡ってきたら、サーバーにハッシュ化前のnonceとともにid_tokenを送付し、サーバーはハッシュ前のnonceとid_tokenをチェックして処理を行う。

サインインのコールバックを受けて、アプリがサインインを処理する

SafariでSign in with Appleが成功すると、コールバックURLを呼び出し、Universal Linksでアプリが起動することになります。
その際のエントリーポイントは、AppDelegateの application(_:continue:restorationHandler:) になります。

userActivity.webpageURL を取得して、queryを確認します。
前出のstateのチェックをまず行います。
webpageURLに含まれるstateが、サインイン時のStateと同一かどうかを確認します。
正しくサインインされている場合はid_tokenが含まれているので、iOS 13と同様のサインイン処理を行います。

    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
        guard let url = userActivity.webpageURL else { return false }
        let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems
        let state = queryItems?.first(where: { $0.name == "state" })?.value
        let idToken = queryItems?.first(where: { $0.name == "id_token" })?.value
        // 以下、stateの確認とidTokenの処理を行う
        return true
    }

以上で、iOS 13と同様のSign in with Appleの機能を実装できるようになりました。

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?