アップルの開発者向けイベント WWDC2019 が6月3日(現地時間)に開催され、iOS13や、予ねてより噂されていたダークモードなど目につく発表が多かったように思います。

今回は、 WWDC2019の話に行く前に、去年の WWDC2018で発表された AuthenticationServices をついて改めて調べてみました。
iOS12が去年9月にリリースされて、1Password などのサードパーティー製管理アプリのオートフィルを実装しているアプリもよく見かけるようになりました。まずは、しっかり復習して新しい技術をキャッチアップして行きたいと思います。

TL;DR

  • できる事
    • パスワードのオートフィル機能(Password AutoFill)
      • サードパーティー製管理アプリもサポート。起動しないで自動入力可能
    • 強力なパスワード自動生成(Automatic Strong Passwords)
    • SMS で送信されてきたワンタイムパスコードの自動入力(Security Code AutoFill)
    • Safari と関連するアプリ間でログインセッションを共有可能(Federated authentication)

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

Password AutoFill

  • Safari や iCloud keychain に保存しておいたパスワードのオートフィル機能
  • Safari やアプリで、パスワード管理アプリを起動せずに、そのアプリに保存されたパスワードの自動入力が可能
    • 1Password ・ LastPass ・ Dashlane ・ Keeper ・ Remembear
    • 有効にするにはユーザが端末設定から操作する必要がある
      • 端末設定 -> パスワードとアカウント -> パスワードを自動入力、連携するパスワード管理アプリを許可

引用元: Apple Documentation About the Password AutoFill Workflow

有効になる流れ

  1. iOS デバイスにアプリ インストール
  2. iOS が、 Xcode 内 Associated Domains Entitlement に設定したドメインの Credential ファイル( apple-app-site-association )をダウンロードする
  3. すべて成功した場合
    • iOS はアプリをそのドメインに関連付ける
    • そのドメインの認証情報に対してパスワードの自動入力を有効にする

実装手順

Password AutoFill :https://developer.apple.com/documentation/security/password_autofill

Web サーバの設定
  • Credential ファイルの設置
    • Credential はファイル名を apple-app-site-association とする
    • 12345ABCDE は Team ID
    • jp.upfrontier.sample はアプリの Bundle Identifier
    {
        "webcredentials": {
            "apps": [ "12345ABCDE.jp.upfrontier" ]
        }
    }
    
  • .well-knownディレクトリをルートディレクトリに作成し、その直下に配置

  • https サーバを想定。 http の場合 apple-app-site-association を TLS 証明書で署名する必要がある

Apple Developer Program の設定
  1. Certificates, Identifiers & Profiles -> Identifier -> App IDs を選択
  2. 利用する App IDs を編集して Associated DomainsEnabled に変更
Xcode の設定
  1. Capablilties -> Associated DomainsONにする
  2. Credential を配置した Web サーバのドメインを記述

    webcredentials:example.com
    
  3. UITextField 、 UITextView の textContentType プロパティの設定

    usernameField.textContentType = .username
    passwordField.textContentType = .password
    

Automatic Strong Passwords

  • iOS が強力なパスワードが自動生成し、そのパスワードが画面上でサジェストされる。
  • パスワードは KeyChain に保存され、入力時に生体認証(顔認証、指紋認証)で自動入力することができる

引用元: Apple Documentation About the Password AutoFill Workflow

Automatic Strong Passwords and Security Code AutoFill(セッション動画):https://developer.apple.com/videos/play/wwdc2018/204/

実装

  • 前提として Password AutoFill 機能を実装している
  • ユーザ名の textContentType に .username を指定する
  • パスワードの textContentType に .newPassword を指定する
let userTextField = UITextField()
userTextField.textContentType = .username

let newPasswordTextField = UITextField()
newPasswordTextField.textContentType = .newPassword

ルール

デフォルト

  • 20文字
  • 大文字、数字、ハイフン、小文字を含む
  • エントロピー71bit

カスタムルールを作って自分で定義することも可能

newPasswordTextField.passwordRules = UITextInputPasswordRules(
  descriptor:  "required: lower; required: upper; required: digit; required: [-]; minlength: 8; maxlength: 20;
)

検証用の Web ツールも提供されているようなので、こちらを利用すると良いかと思います。

Customizing Password AutoFill Rules :https://developer.apple.com/documentation/security/passwordautofill/customizingpasswordautofillrules

Security Code AutoFill

  • SMS に届いた2段階認証コード(ワンタイムパスワード)を自動で取得
  • QuickType バーに表示されタップすると自動入力される
  • Mac の Safari 上でも実装可能
  • ワンタイムパスワードしか内容がないメッセージではなぜか認識しない記事あり

引用元: Apple Documentation About the Password AutoFill Workflow

実装

  • 前提として Password AutoFill 機能を実装している
  • textContentType に .oneTimeCode を指定する
let securityCodeTextField = UITextField()
securityCodeTextField.textContentType = .oneTimeCode

Federated authentication

  • Safari と関連するアプリ間でログインセッション情報を共有して、アプリのログインを簡単にすることができる。

関連するクラス

  • ASWebAuthenticationSession
    • iOS12以上
  • SFAuthenticationSession
    • iOS11のみ、 iOS12で非推奨
  • SFSafariViewController
    • iOS9以上
    • Safari の標準機能を備えた ViewController を作成
    • Safari ブラウザと Cookie やウェブサイトのデータの共有が可能
    • カスタマイズ出来る部分が限られる
    • Storyboard から作成することができない
    • 実装がシンプル

変遷

  • iOS9
    • SFSafariViewController で、 Safari ブラウザと Cookie やウェブサイトのデータ共有可能
  • iOS11
    • SFSafariViewController に仕様変更あり
        Cookie 等のデータが Safari とは独立した領域に保存されるようになった (ユーザーのプライバシーをより強固に保護)
    • SFAuthenticationSession を使用することで認証情報を共有が可能
  • iOS12
    • SFAuthenticationSession が非推奨
    • 代わりにASWebAuthenticationSession を使用する

実装  ASWebAuthenticationSession 、 SFAuthenticationSession

class AuthenticationSession: AuthenticationSessionProtocol {

    private let innerAuthenticationSession: AuthenticationSessionProtocol

    required init(url URL: URL,
         callbackURLScheme: String?,
         completionHandler: @escaping (URL?, Error?) -> Void) {

        if #available(iOS 12, *) {
            innerAuthenticationSession = ASWebAuthenticationSession(url: URL, callbackURLScheme: callbackURLScheme, completionHandler: completionHandler)
        } else {
            innerAuthenticationSession = SFAuthenticationSession(url: URL, callbackURLScheme: callbackURLScheme, completionHandler: completionHandler)
        }
    }

    func start() -> Bool {
        return innerAuthenticationSession.start()
    }

    func cancel() {
        innerAuthenticationSession.cancel()
    }
}

引用元:Wrapper design pattern by example using SFAuthenticationSession and ASWebAuthenticationSession

実装  SFSafariViewController

import UIKit
import SafariServices

class ViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        let vc = SFSafariViewController(URL: NSURL(string: "https://sample.jp")!)
        vc.delegate = self
        presentViewController(vc, animated: true, completion: nil)
    }
}

extension ViewController: SFSafariViewControllerDelegate {
    func safariViewController(_ controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) {
        print("didCompleteInitialLoad")
    }
    func safariViewController(_ controller: SFSafariViewController, activityItemsFor URL: URL, title: String?) -> [UIActivity] {
        print("activityItemsForURL")
        return []
    }
    func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
        print("safariViewControllerDidFinish")
    }
}

まとめ

いかがだったでしょうか?そこまで実装コストは高くはないと思います。
ユーザのパスワード入力の手間を軽減させて、パスワードというものをそこまで意識させずにアプリに入ってきてもらう、素敵な機能だと思います。ぜひ今後も積極的に使って行きたいです。


参考文献