Developer's Blog

【iOS 11】 Web認証用の新クラス「SFAuthenticationSession」

こんにちは、アプリケーション共同開発部の中澤です。

先日、「iOS 11 で Safari View Controller の Cookie 等の共有機能が無くなる (予定)」という記事で、 Safari View Controller の仕様変更について「不便になるなぁ」などとボヤいておりましたが、あれから少し後、 SafariServices フレームワークに新しく SFAuthenticationSession というクラスが追加されました。これを使うことで、 Safari View Controller の当初の利便性をある程度保って Web 認証が出来るようになりました。🎉

以下、その詳細についてご紹介します。

SFAuthenticationSession とは?

SFAuthenticationSession は対象の Web サービスの API トークン を取得するための Web 認証フローをユーザーに提供するクラスです。

フローとは、以下のようなものです。

  1. アラートを表示し、 Safari の Cookie 等データを共有して良いかをユーザーに尋ねる
    • ユーザーが許可しなかった場合はそれ以上先には進まない
  2. Safari とセッションを共有可能な Web ビューが表示され、そこでユーザーは認証を行う
  3. 認証完了時、サーバーから認証情報 (API トークン等) を iOS 側で受け取る

Safari View Controller ではダメなの?

今の Safari View Controller よりはもう少し、ユーザーも開発者も楽になります。

前回の記事では以下のような事をお話していました。

  1. iOS 11 では Safari View Controller が Safari と Cookie を共有しなくなった
  2. それによってユーザーのプライバシーがより強固に保護されるようになった
  3. しかしログイン情報も共有されなくなるのでアプリごとにログインする必要がある
    • ちょっと面倒

SFAuthenticationSession はこの 3 番目の「ちょっと面倒」を緩和してくれるクラスとも言えます。

SFAuthenticationSession が出す Web ビューは、従来の Safari View Controller のように Safari と全ての Cookie を共有します。この挙動のおかげで SNS 等を利用したシングルサインオンでもログイン情報をアプリごとに入力する必要が無くなります。

また、 Safari View Controller を使って実装していた頃は、APIトークンを受け取るためのコールバック処理に AppDelegate の openURL(_:) を経由していましたが、 SFAuthenticationSession ではクロージャに書くようになったので、開発者は認証に関する処理を一箇所にまとめやすくなりました。

注意

このクラスが登場したからと言って Safari View Controller の Cookie はやはり Safari とは別の領域に保存されるので注意が必要です。

例えば、既に SFAuthenticationSession を採用している Google 社の Gmail アプリの場合を考えてみましょう。

このアプリ上で Google アカウントを使ってログインする際は、アプリが SFAuthenticationSession を出してくれるので、 Safari のログイン状態を引き継いでログイン出来ます。

一方、アプリ内でメールを閲覧する際は話が変わってきます。Gmailアプリでは、メールに含まれるリンクをタップした際に出る Web ビューの 1 つとして Safari View Controller が使われています。その為、もしそのリンク先がログインが必要なページだった場合、 Safari で既にログインしていても再度ログイン情報を入力する必要があります。

ただ、それでも Safari View Controller には、 Safari へ移動するボタンや、サービス呼び出し (UIActivityViewController) ボタンもあるので、 UIWebView しか選択肢が無かった時代に比べればマシだと思います。

実装方法

今回はサンプルとして、 「Show Auth Page」というボタンを表示し、それが押された時にローカルに立てたサーバーへ Web 認証 を行うという処理を SFAuthenticationSession を使って書いてみます。

コードとしては以下のとおりです。

class ViewController: UIViewController {
    // ③
    var authSession: SFAuthenticationSession?
    
    @IBAction func showAuthPage(_ sender: Any) {
        let url = URL(string:"http://127.0.0.1:8080/auth")!
        let callbackURLScheme = "my-sfauthsession-scheme"
        
        // ①
        authSession = SFAuthenticationSession(
            url: url,
            callbackURLScheme: callbackURLScheme,
            completionHandler: { (callbackURL, error) in
                if let callbackURL = callbackURL {
                    print("callbackURL: \(callbackURL)")
                }
        })
        
        // ②
        authSession?.start()
    }
}

以下、コード内の ① 〜 ③ についての補足です。 (番号がバラバラですみません!)

① SFAuthenticationSession のインスタンス作成時に以下を指定しています。

      • url
        • Web 認証のエントリーポイントとなる URIです。
        • 今回はローカルに立てた動作確認用サーバー (後述) にアクセスするので、http://127.0.0.1:8080/auth としています。
      • callbackURLScheme
        • ここには認証完了後に後述の completionHandler を呼び出すためのコールバック URI のスキームを指定します。
        • ここで指定しなくても、 Info.plist で指定出来る従来の カスタム URI スキームを使うことも可能です。ただ、 Info.plist だと 第三者でも ipa から簡単に知る事ができ、認証情報を悪意あるアプリに横取りされるリスクを無闇に広めてしまうので、オススメはしません。
      • completionHandler: 認証が終わる時、成功・失敗に関わらずここで設定したクロージャが呼び出されます。
        • 成功時: 多くの場合、 callbackURL にサーバーが詰めてくれた API トークンが入っていることでしょう。適宜それを取り出して使いましょう。
        • 失敗時 (アラートや Web ビューのキャンセル押下時) : callbackURL には nil が入り、 error にはエラー詳細が入っています。
        • 後述の cancel() 実行時はこれは実行されません。

② 作成した SFAuthenticationSession インスタンスの start() を呼び出し、Web 認証を開始しています。
また、認証フロー開始後にアプリ側から認証を中断させたい場合は cancel() を使います。

③ SFAuthenticationSession のインスタンスはどこかで参照しておかないと開放されてしまう為、ここではプロパティとして保持しています。インスタンスが開放される際は cancel() を実行するのと同じように SFAuthenticationSession が表示するアラートや Web ビューも共に破棄されてしまいます。

今回は動作確認用に以下のサーバーサイド実装も書いてみました。 (Swift で書かれた Web フレームワーク「 Kitura 」を使っています。)

import Kitura
let router = Router()
router.get("/") { request, response, next in
    try response.redirect("my-sfauthsession-scheme://token?v=12345678")
    next()
}
Kitura.addHTTPServer(onPort: 8080, with: router)
Kitura.run()

上記はリクエストが来たらアプリのコールバック URI ( my-sfauthsession-scheme://token?v=12345678 ) へリダイレクトするだけの簡単なものです。

これを使って動作確認した結果がこちらです。

うまくコールバック URI を取得できていることが分かります。

まとめ

API トークンを取得する形の Web 認証には、SFAuthenticationSession を使うことでより良い UX をユーザーに提供出来るでしょう。

SFSafariViewController の制限についても、 UIWebView しか選択肢が無かった頃に比べれば許容範囲ではと思います。

この SFAuthenticationSession 、 OAuth ベースの Web 認証を iOS で行う機会があればドンドン使ってゆきたいですね。

 

Copyright © 2019 Fenrir Inc. All rights reserved.