Slide 1

Slide 1 text

栗山 徹 (@kotetu) - iOSDC Japan 2023 (2023/09/03) 認証体験向上のために passkeys (パスキー) に対応する 〜 メリット・対応方法について 1

Slide 2

Slide 2 text

• 対象となる方 • passkeysに興味あるが、あまりピンときていない方 • passkeysの導入を検討している方 • セッション概要 • passkeys(パスキー)の概要や導入のメリット • アプリの実装 はじめに 2

Slide 3

Slide 3 text

(くりやま とおる) • X (Twitter) : @kotetu • 株式会社アンドパッド テックリード • パンフレット原稿も書きました! • Swift Markdownを使ったMarkdown アプリ開発 栗山 徹 3

Slide 4

Slide 4 text

1. passkeysとは? 2. passkeysのしくみ 3. iOSアプリにおけるpasskeys実装方法 4. まとめ アジェンダ 4

Slide 5

Slide 5 text

1. passkeysとは? 5

Slide 6

Slide 6 text

• 次世代認証技術 • 生体認証等のスマホ・PCが持つ技術を組み合わせてパスワードレスでの認証 を実現 • passkeysに対応したサービスが増えてきた • Google認証 • GitHub認証 (パブリックベータ版) passkeys (パスキー) 6

Slide 7

Slide 7 text

• ユーザーIDのみ入力 • パスワードは入力せずにFace ID (Touch ID)認証 passkeysを使ったサインアップ 7

Slide 8

Slide 8 text

• 端末に登録済みのpasskey認証情報を選択するだけ • ID・パスワード入力不要 passkeysを使ったサインイン 8

Slide 9

Slide 9 text

• パスワード認証の課題を改善するため • セキュリティ面での課題 • ユーザビリティ面での課題 passkeysが登場した背景 9

Slide 10

Slide 10 text

• サインアップ時にサーバーへIDとパスワード(ソルト付きハッシュ化した もの)が保存される • サインイン時は入力されたID・パスワードを登録時の情報と照合 (おさらい) パスワード認証 10

Slide 11

Slide 11 text

• 各種攻撃への構造的な脆弱性 • フィッシング • クレデンシャルスタッフィング セキュリティ面での課題 11

Slide 12

Slide 12 text

• 実在する組織を騙って偽サイトへ誘導し、アカウント情報を不正取得 • 巧妙化し、被害数も増加傾向 フィッシング 12

Slide 13

Slide 13 text

• 不正取得したID・パスワードを使用して不正ログインを試みる手法 • パスワードを使い回している場合は被害が大きくなる クレデンシャルスタッフィング 13

Slide 14

Slide 14 text

passkeysはフィッシングにも クレデンシャルスタッフィングにも 強い 14

Slide 15

Slide 15 text

• サインアップ・サインイン時のパスワード入力が不要 • サーバーにパスワード情報が保存されない • 認証情報取得目的でのサーバーへの不正アクセスの意味が無くなる • サービス・ユーザー毎に自動で認証情報が作成される • 仕組み的に使い回しが発生しない なぜ有効なのか? 15

Slide 16

Slide 16 text

• パスワード管理の手間 • サービスの数だけパスワード • 文字数・文字種の制約 • 頻繁に求められるパスワード入力 ユーザビリティ面での課題 16

Slide 17

Slide 17 text

passkeysは ユーザビリティ面でも メリットがある 17

Slide 18

Slide 18 text

• パスワード管理の手間から解放される • サインアップ・サインインが迅速に行える • 端末間で認証情報をセキュアに共有可能に • 機種変更による移行の手間を軽減 passkeysのユーザビリティ面のメリット 18

Slide 19

Slide 19 text

2. passkeysのしくみ 19

Slide 20

Slide 20 text

• パスワードの代わりに端末が生成した暗号鍵を使用して認証を行う • 公開鍵暗号方式を使用 • 認証情報はiCloud キーチェーン(Appleプラットフォームの場合)を通じて端末 間共有可能 • FIDO認証、 WebAuthnに準拠 passkeysの技術概要 20

Slide 21

Slide 21 text

• passkeysのベースになる技術 • 秘密鍵・公開鍵という鍵のペアを使用して暗号化・復号を行う • HTTPS通信、デジタル署名で利用される 公開鍵暗号方式 21

Slide 22

Slide 22 text

• 認証器 (iOS/iPadOS/macOSが担当) • 生体認証 (Face ID or Touch ID) • 暗号鍵(公開鍵・秘密鍵)の生成と管理 • サーバー • IDと認証器で作成された公開鍵のペアを認証情報として保持 • アプリ • 認証器 - サーバー間の中継 passkeysにおける登場人物 22

Slide 23

Slide 23 text

ೝূث ೝূ੒ޭ ൿີ伴ɾެ։伴 ੜ੒ ΞϓϦ ೝূɾ νϟϨϯδ΁ͷॺ໊Λཁٻ αʔόʔ ॺ໊͞Εͨ νϟϨϯδ Λݕূ Ϣʔβʔ αΠϯΞοϓૢ࡞ νϟϨϯδཁٻ Face ID, Touch ID ೝূ νϟϨϯδฦ٫ ೖྗ ެ։伴ͱ ൿີ伴Ͱॺ໊͞ΕͨνϟϨϯδ Λฦ٫ ެ։伴ͱ ॺ໊͞ΕͨνϟϨϯδ Λ౉͢ ݕূ੒ޭ ೝূ੒ޭ αΠϯΞοϓ੒ޭදࣔ • チャレンジ • ランダムな文字列、リクエスト 毎に生成 • 認証(証明書の検証)で使用 • 署名されたチャレンジの検証 • 一緒に渡された公開鍵を使用  して検証 サインアップの流れ 23

Slide 24

Slide 24 text

ೝূث ೝূ੒ޭ ΞϓϦ ೝূɾ νϟϨϯδ΁ͷॺ໊Λཁٻ αʔόʔ ॺ໊͞Εͨ νϟϨϯδ Λݕূ Ϣʔβʔ αΠϯΠϯૢ࡞ νϟϨϯδཁٻ Face ID, Touch ID ೝূ νϟϨϯδฦ٫ ೖྗ ൿີ伴Ͱॺ໊͞ΕͨνϟϨϯδ Λฦ٫ ॺ໊͞ΕͨνϟϨϯδ Λ౉͢ ݕূ੒ޭ ೝূ੒ޭ αΠϯΠϯ੒ޭදࣔ • 保存済みpasskeyの選択 • Face ID / Touch ID認証の前に 選択を行う • 署名されたチャレンジの検証 • サインアップ時に保存された 公開鍵を使って検証を行う サインインの流れ 24

Slide 25

Slide 25 text

3. iOSアプリにおける passkeys実装方法 25

Slide 26

Slide 26 text

• Associated Domains を設定する必要がある • AuthenticationServices フレームワークを使用する • アプリ - 認証器間のやり取りのためのI/F • ユーザーへ認証UIを表示 • アプリ - サーバー間のやり取りのための I/F は含まれない • 自前で用意する必要がある 対応手順概要 26

Slide 27

Slide 27 text

• apple-app-site-association (AASA) を設置する • XcodeのAssociated Domains を設定する 対応手順 (Associated Domainsの設定) 27

Slide 28

Slide 28 text

• 以下を記載したjsonファイルをサーバーにデプロイする 対応手順 (Associated Domainsの設定) apple-app-site-association (AASA) を設置する { "webcredentials": { "apps": [ “XXXXXXXXXX.com.example.passkeys" ] } } 28

Slide 29

Slide 29 text

• AASAを設置したサーバーのドメインを設定する 対応手順 (Associated Domainsの設定) Xcode の Associated Domains を設定する 29

Slide 30

Slide 30 text

• サーバーから challenge を取得する • ASAuthorizationRequest を取得する • 認証画面を表示し、FaceID or TouchID で認証 • ASAuthorizationControllerDelegate で認証結果を受け取る • サーバーへリクエストを送り、最終的な認証結果を受け取る 対応手順 (サインアップ) 30

Slide 31

Slide 31 text

• サーバー・クライアント間の通信仕様は定義されていない • サーバー側でリクエスト毎にランダムな文字列を生成する • アプリ側は取得した challenge を Data 型に変換する 対応手順 (サインアップ) サーバーから challenge を取得する 31

Slide 32

Slide 32 text

• ASAuthorizationPlatformPublicKeyCredentialProvider を使用する • createCredentialRegistrationRequest メソッド • name : ユーザー名 (表示名なので一意でなくても良い) • userID : 一意のID (一意である必要がある) 対応手順 (サインアップ) ASAuthorizationRequest を取得する let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: domain) let registrationRequest = publicKeyCredentialProvider.createCredentialRegistrationRequest( challenge: challenge, name: userName, userID: userID ) 32

Slide 33

Slide 33 text

• ASAuthorizationController を使用する • delegate (ASAuthorizationControllerDelegate) を設定 • presentationContextProvider (ASAuthorizationControllerPresentationContextProviding) を設定 • performRequests メソッドを呼び出すと表示される 対応手順 (サインアップ) 認証画面を表示し、FaceID or TouchID で認証 let authController = ASAuthorizationController(authorizationRequests: [ registrationRequest ] ) authController.delegate = self authController.presentationContextProvider = self authController.performRequests() 33

Slide 34

Slide 34 text

• 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

Slide 35

Slide 35 text

• ASAuthorizationPlatformPublicKeyCredentialAssertion から下記3プロパテ ィを取り出してサーバーへ送る • rawAttestationObject / rawClientDataJSON / credentialId • サーバーから認証結果を受け取り、画面に反映する 対応手順 (サインアップ) サーバーへリクエストを送り、最終的な認証結果を受け取る let rawAttestationObject = credentialRegistration.rawAttestationObject let credentialID = credentialRegistration.credentialID let rawClientDataJSON = credentialRegistration.rawClientDataJSON // TODO: αʔόʔ΁্هσʔλΛૹΓɺฦ͖ͬͯͨೝূ݁ՌΛը໘΁൓ө͢Δ 35

Slide 36

Slide 36 text

• サインアップと処理の流れは同じ • 呼び出すメソッドやデータ型が一部異なる • 2種類の認証時UI • モーダル画面を表示して認証 • QuickType バーに passkey 情報を表示して認証 対応手順 (サインイン) 36

Slide 37

Slide 37 text

• 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

Slide 38

Slide 38 text

• 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

Slide 39

Slide 39 text

QuickType バーに passkey 情報を表示して認証 (1 / 2) • ユーザ名を入力する UITextField の textContentType を .username に設定す る • 設定した UITextField にフォーカスする と QuickType バーに登録済み passkey 情報が表示される 対応手順 (サインイン) userNameField.textContentType = .username 39

Slide 40

Slide 40 text

• 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

Slide 41

Slide 41 text

4. まとめ 41

Slide 42

Slide 42 text

• passkeys はパスワード認証の置き換えを目指して考案された • passkeys に対応することのメリット • セキュリティ面 (フィッシング等の被害を防止できる) • ユーザビリティ面 (パスワード管理の手間から解放される) • アプリの実装には AuthenticationServices フレームワークを使用 • アプリ - サーバー間のI/Fは自前で用意する必要がある まとめ 42

Slide 43

Slide 43 text

• 建設業界未経験でも大歓迎!! • ANDPADブース出展中! 積極採用中! 43 求人情報はこちら!

Slide 44

Slide 44 text

後夜祭 iOSDC Japan 2023 • 9/26 19:00 ʙ 20:30 • オンライン開催 アフターイベント開催します! 44 connpassはこちら! アンドパッド 西 登壇!

Slide 45

Slide 45 text

モバイルアプリのDevOps戦略で実現する高速デリバリーLT • 9/28 12:00 ʙ 13:00 • オンライン開催 LT登壇します! 45 connpassはこちら!