Share
Embed
認証体験向上のために passkeys (パスキー) に対応する 〜 メリット・対応方法について / iosdcjapan-2023-passkeys
iOSDC Japan 2023 の トーク「認証体験向上のためにpasskeys(パスキー)に対応する 〜 メリット・対応方法について」の発表資料です。
プロポーザル
https://fortee.jp/iosdc-japan-2023/proposal/b603f798-e7f5-487d-a8f0-7d48d952fad1
kotetu (kotetuco)
September 03, 2023
Transcript
45 slides
-
栗山 徹 (@kotetu) - iOSDC Japan 2023 (2023/09/03) 認証体験向上のために passkeys
(パスキー) に対応する 〜 メリット・対応方法について 1 -
(くりやま とおる) • X (Twitter) : @kotetu • 株式会社アンドパッド テックリード
• パンフレット原稿も書きました! • Swift Markdownを使ったMarkdown アプリ開発 栗山 徹 3 -
• 次世代認証技術 • 生体認証等のスマホ・PCが持つ技術を組み合わせてパスワードレスでの認証 を実現 • passkeysに対応したサービスが増えてきた • Google認証 •
GitHub認証 (パブリックベータ版) passkeys (パスキー) 6 -
• パスワードの代わりに端末が生成した暗号鍵を使用して認証を行う • 公開鍵暗号方式を使用 • 認証情報はiCloud キーチェーン(Appleプラットフォームの場合)を通じて端末 間共有可能 • FIDO認証、
WebAuthnに準拠 passkeysの技術概要 20 -
• 認証器 (iOS/iPadOS/macOSが担当) • 生体認証 (Face ID or Touch ID)
• 暗号鍵(公開鍵・秘密鍵)の生成と管理 • サーバー • IDと認証器で作成された公開鍵のペアを認証情報として保持 • アプリ • 認証器 - サーバー間の中継 passkeysにおける登場人物 22 -
ೝূث ೝূޭ ൿີ伴ɾެ։伴 ੜ ΞϓϦ ೝূɾ νϟϨϯδͷॺ໊Λཁٻ αʔόʔ ॺ໊͞Εͨ νϟϨϯδ
Λݕূ Ϣʔβʔ αΠϯΞοϓૢ࡞ νϟϨϯδཁٻ Face ID, Touch ID ೝূ νϟϨϯδฦ٫ ೖྗ ެ։伴ͱ ൿີ伴Ͱॺ໊͞ΕͨνϟϨϯδ Λฦ٫ ެ։伴ͱ ॺ໊͞ΕͨνϟϨϯδ Λ͢ ݕূޭ ೝূޭ αΠϯΞοϓޭදࣔ • チャレンジ • ランダムな文字列、リクエスト 毎に生成 • 認証(証明書の検証)で使用 • 署名されたチャレンジの検証 • 一緒に渡された公開鍵を使用 して検証 サインアップの流れ 23 -
ೝূث ೝূޭ ΞϓϦ ೝূɾ νϟϨϯδͷॺ໊Λཁٻ αʔόʔ ॺ໊͞Εͨ νϟϨϯδ Λݕূ Ϣʔβʔ
αΠϯΠϯૢ࡞ νϟϨϯδཁٻ Face ID, Touch ID ೝূ νϟϨϯδฦ٫ ೖྗ ൿີ伴Ͱॺ໊͞ΕͨνϟϨϯδ Λฦ٫ ॺ໊͞ΕͨνϟϨϯδ Λ͢ ݕূޭ ೝূޭ αΠϯΠϯޭදࣔ • 保存済みpasskeyの選択 • Face ID / Touch ID認証の前に 選択を行う • 署名されたチャレンジの検証 • サインアップ時に保存された 公開鍵を使って検証を行う サインインの流れ 24 -
• Associated Domains を設定する必要がある • AuthenticationServices フレームワークを使用する • アプリ -
認証器間のやり取りのためのI/F • ユーザーへ認証UIを表示 • アプリ - サーバー間のやり取りのための I/F は含まれない • 自前で用意する必要がある 対応手順概要 26 -
• 以下を記載したjsonファイルをサーバーにデプロイする 対応手順 (Associated Domainsの設定) apple-app-site-association (AASA) を設置する { "webcredentials":
{ "apps": [ “XXXXXXXXXX.com.example.passkeys" ] } } 28 -
• サーバーから challenge を取得する • ASAuthorizationRequest を取得する • 認証画面を表示し、FaceID or
TouchID で認証 • ASAuthorizationControllerDelegate で認証結果を受け取る • サーバーへリクエストを送り、最終的な認証結果を受け取る 対応手順 (サインアップ) 30 -
• サーバー・クライアント間の通信仕様は定義されていない • サーバー側でリクエスト毎にランダムな文字列を生成する • アプリ側は取得した challenge を Data 型に変換する
対応手順 (サインアップ) サーバーから challenge を取得する 31 -
• ASAuthorizationPlatformPublicKeyCredentialProvider を使用する • createCredentialRegistrationRequest メソッド • name : ユーザー名
(表示名なので一意でなくても良い) • userID : 一意のID (一意である必要がある) 対応手順 (サインアップ) ASAuthorizationRequest を取得する let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: domain) let registrationRequest = publicKeyCredentialProvider.createCredentialRegistrationRequest( challenge: challenge, name: userName, userID: userID ) 32 -
• ASAuthorizationController を使用する • delegate (ASAuthorizationControllerDelegate) を設定 • presentationContextProvider (ASAuthorizationControllerPresentationContextProviding)
を設定 • performRequests メソッドを呼び出すと表示される 対応手順 (サインアップ) 認証画面を表示し、FaceID or TouchID で認証 let authController = ASAuthorizationController(authorizationRequests: [ registrationRequest ] ) authController.delegate = self authController.presentationContextProvider = self authController.performRequests() 33 -
• authorizationController(controller:didCompleteWithAuthorization:) • authorization.credential を ASAuthorizationPlatformPublicKeyCredentialRegistration へキャスト 対応手順 (サインアップ) ASAuthorizationControllerDelegate
で認証結果を受け取る func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { switch authorization.credential { case let credentialRegistration as ASAuthorizationPlatformPublicKeyCredentialRegistration: . . . default: fatalError("Received unknown authorization type.") } } 34 -
• ASAuthorizationPlatformPublicKeyCredentialAssertion から下記3プロパテ ィを取り出してサーバーへ送る • rawAttestationObject / rawClientDataJSON / credentialId
• サーバーから認証結果を受け取り、画面に反映する 対応手順 (サインアップ) サーバーへリクエストを送り、最終的な認証結果を受け取る let rawAttestationObject = credentialRegistration.rawAttestationObject let credentialID = credentialRegistration.credentialID let rawClientDataJSON = credentialRegistration.rawClientDataJSON // TODO: αʔόʔ্هσʔλΛૹΓɺฦ͖ͬͯͨೝূ݁ՌΛը໘ө͢Δ 35 -
• サインアップと処理の流れは同じ • 呼び出すメソッドやデータ型が一部異なる • 2種類の認証時UI • モーダル画面を表示して認証 • QuickType
バーに passkey 情報を表示して認証 対応手順 (サインイン) 36 -
• createCredentialRegistrationRequest メソッド • createCredentialAssertionRequest メソッドへ変更 • challenge のみ指定 対応手順
(サインイン) モーダル画面を表示して認証 (1 / 2) let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: domain) let assertionRequest = publicKeyCredentialProvider.createCredentialAssertionRequest(challenge: challenge) let authController = ASAuthorizationController(authorizationRequests: [ assertionRequest ] ) authController.delegate = self authController.presentationContextProvider = self authController.performRequests() 37 -
• authorizationController(controller:didCompleteWithAuthorization:) • authorization.credential を ASAuthorizationPlatformPublicKeyCredentialAssertion へキャスト 対応手順 (サインイン) モーダル画面を表示して認証
(2 / 2) func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { switch authorization.credential { case let credentialAssertion as ASAuthorizationPlatformPublicKeyCredentialAssertion: let credentialID = credentialAssertion.credentialID let rawClientDataJSON = credentialAssertion.rawClientDataJSON let signature = credentialAssertion.signature let rawAuthenticatorData = credentialAssertion.rawAuthenticatorData let userID = credentialAssertion.userID // TODO: αʔόʔϦΫΤετ default: fatalError("Received unknown authorization type.") } } 38 -
QuickType バーに passkey 情報を表示して認証 (1 / 2) • ユーザ名を入力する UITextField
の textContentType を .username に設定す る • 設定した UITextField にフォーカスする と QuickType バーに登録済み passkey 情報が表示される 対応手順 (サインイン) userNameField.textContentType = .username 39 -
• performAutoFillAssistedRequests メソッドを呼び出す • performAutoFillAssistedRequests メソッドを呼び出さないと QuickType バーに表示されない 対応手順 (サインイン)
QuickType バーに passkey 情報を表示して認証 (2 / 2) let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: domain) let assertionRequest = publicKeyCredentialProvider.createCredentialAssertionRequest(challenge: challenge) let authController = ASAuthorizationController(authorizationRequests: [ assertionRequest ] ) authController.delegate = self authController.presentationContextProvider = self authController.performAutoFillAssistedRequests() 40 -
• passkeys はパスワード認証の置き換えを目指して考案された • passkeys に対応することのメリット • セキュリティ面 (フィッシング等の被害を防止できる) •
ユーザビリティ面 (パスワード管理の手間から解放される) • アプリの実装には AuthenticationServices フレームワークを使用 • アプリ - サーバー間のI/Fは自前で用意する必要がある まとめ 42 -
後夜祭 iOSDC Japan 2023 • 9/26 19:00 ʙ 20:30 •
オンライン開催 アフターイベント開催します! 44 connpassはこちら! アンドパッド 西 登壇!