Slide 1

Slide 1 text

iOSとServer Side Swiftで実装する パスキーの段階的導⼊ 〜新規登録と既存パスワードユーザー移⾏の実践〜 藤⾨ 莉⽣ / Rio Fujimon iOSDC Japan 2026 Track D 2026/09/12

Slide 2

Slide 2 text

⾃⼰紹介 藤⾨ 莉⽣ Rio Fujimon iOSアプリエンジニア 名刺アプリ Eight のiOS版を開発 X @RioFujimon GitHub @rfjmn 発表資料 Foundation Model × VisionKit で 実現するローカル OCR Foundation Models を活⽤するための Tips 2

Slide 3

Slide 3 text

Passkeyの概要 公開鍵暗号⽅式を使う、パスワード不要の認証⽅法 利⽤者の端末側 秘密鍵 サービスのServer 対になる鍵ペア 公開鍵(登録済み) 認証器が管理 アカウントと紐づけて保存 秘密鍵で署名を⽣成 公開鍵で署名を検証 署名:対象のデータに対して、秘密鍵を使って⽣成する値。 対応する公開鍵を使い、そのデータに対する署名が正しいか検証できる。 秘密鍵と⽣体情報は、サービスのServerへ送らない 3

Slide 4

Slide 4 text

iOSアプリにおけるPasskeyの役割分担 iOSアプリ Credential Manager サービスのServer OSへ処理を要求 認証情報を管理する機能 サービス側で検証 ● 登録:Passkeyを作成 ● 認証:登録済みの Passkeyで署名を⽣成 例:Appleの「パスワード」 ● Passkeyの保存‧選択 ● Passkeyの端末間同期 (同期対応の場合) ● Challengeを発⾏ Serverが発⾏する⼀度限りの ランダム値 ● アプリから届いた登録‧認 証データを検証 Serverへデータを送信 Authenticator(認証器) 検証後の処理 登録:公開鍵など 認証:署名など 鍵ペアを⽣成 秘密鍵で署名を⽣成 登録:公開鍵を保存 認証:ログインを許可 4

Slide 5

Slide 5 text

Passkey登録の仕組み 公開鍵とCredential ID(認証情報のID)を、アカウントに紐づけて保存する iOSアプリ Authenticator(認証器) サービスのServer ① 登録に使う情報を渡す Challenge(Serverが発⾏する⼀度限りのランダム値) RP ID(サービスを識別するドメイン名)‧User ID(アカウントID)など ② Passkeyの作成を要求 鍵ペアを⽣成 登録データ 公開鍵‧Credential IDなど 秘密鍵は認証器が管理 公開鍵を登録データに含める ③ 登録データをServerへ送信 公開鍵‧Credential ID‧Challenge‧Origin(要求元)など 秘密鍵は、サービスのServerへ 送らない ④ 登録データを検証 Challenge‧Origin‧RP IDなど 公開鍵‧Credential IDを アカウントに紐づけて保存 5

Slide 6

Slide 6 text

Passkey認証の仕組み 登録済みの公開鍵で署名を検証し、ログインを許可する iOSアプリ Authenticator(認証器) サービスのServer ① 認証に使う情報を渡す Challenge(Serverが発⾏する⼀度限りのランダム値) ② 利⽤者の確認‧署名を要求 署名付き認証データ(Assertion) Credential ID(認証情報のID) 署名など 登録済みの秘密鍵を使⽤ Face ID‧PINで本⼈確認 署名を⽣成 ③ 署名付き認証データをServerへ送信 Credential ID‧署名‧Challenge‧Origin(要求元)など 新しい鍵は作らず、 登録済みの鍵を利⽤する ⑤ アクセストークンを返す (ログイン状態を⽰す値) ④ 署名付き認証データを検証 登録済みの公開鍵を使⽤ 認証セッション(ログイン状態)を発⾏ 6

Slide 7

Slide 7 text

サンプルアプリ:Passkeyへ移⾏ パスワード認証 Passkey登録完了 既存アカウントに Passkeyを追加 保存済みパスワードを⾃動⼊⼒して認証。 条件を満たすとPasskeyが追加される。 次回から、作成したPasskeyで ログインできる。 既存ユーザーを再現するため、 パスワードでの新規登録‧ログインも実装。 7

Slide 8

Slide 8 text

サンプルアプリ:Passkeyで新規登録 ⼊⼒フォーム システムシート 新しいアカウントと Passkeyを作成 メールアドレス‧表⽰名を、 フォームへの⼊⼒または システムシートでの選択で指定。 登録後は、作成したPasskeyで ログインできる。 新規登録とログインは、別の操作として実装。 8

Slide 9

Slide 9 text

サンプルアプリ:Passkeyでログイン Passkeyを選択 ログイン完了 登録済みPasskeyで パスワードなしのログイン Passkeyを選択し、Face IDなどで本⼈確認。 認証に成功するとログイン状態になる。 移⾏‧新規登録のどちらで作成した Passkeyでも利⽤できる。 9

Slide 10

Slide 10 text

利⽤したフレームワーク‧ライブラリ iOSアプリ Server SwiftUI‧UIKit‧Observation Vapor 画⾯の描画‧構成‧表⽰状態の監視 HTTPリクエスト処理‧HTTPレスポンス⽣成 AuthenticationServices swift-webauthn Passkeyの登録‧認証、 システムシートでの新規登録 登録‧認証オプションの⽣成とデータ検証 Foundation(URLSession) FluentKit‧FluentSQL ServerとのHTTP通信 DBの読み書き‧トランザクション Security(Keychain) FluentPostgresDriver アクセストークンを端末へ保存 FluentとPostgreSQLを接続 保存先:PostgreSQL(データベース) 10

Slide 11

Slide 11 text

iOSアプリ側のアーキテクチャ 処理の呼び出し 処理結果∕表⽰状態の反映 操作 View SwiftUI画⾯ Observationで状態を監視 Presenter 処理 @Observable ⼊⼒と表⽰状態を管理 反映 Interactor actor APIとPasskey処理を調整 結果 APIClient Passkey操作 認証情報Store URLSession ServerのAPIを実⾏ AuthenticationServices 登録‧認証のUIを要求 Keychain アクセストークンを保存 値‧完了‧エラー Router:UIHostingControllerと依存先を組み⽴てる Entity:⼊⼒‧結果‧表⽰状態の型を定義する 11

Slide 12

Slide 12 text

Server側のアーキテクチャ 型への依存 protocolへの適合(実装 → 抽象) PasskeyServer PasskeyApplication Controller UseCase⽤protocol PasskeyPersistence FluentでDBを読み書き Repositoryの実装型 UseCaseの実装型 依存の組み⽴て 具体型を⽣成し、 必要な依存先を注⼊ Repository⽤protocol Verifier⽤protocol PasskeyWebAuthn WebAuthnの変換‧検証 Verifierの実装型 PasskeyDomain:各Targetが利⽤する共通の型 Account‧Credential‧Challengeなど 12

Slide 13

Slide 13 text

アカウントと認証情報のDB構成 accounts にメール‧認証情報‧セッションを紐づける PK=主キー FK=外部キー 親ID=accounts.id accounts アカウント本体(親テーブル) id(PK):Account ID display_name:表⽰名 1:1 1:0..1 1:0..N 1:0..N account_ emails password_ credentials passkey_ credentials authentication_ sessions メールアドレス パスワード認証 Passkey認証 ログイン状態 id:メールID account_id:親ID normalized_email:照合⽤ account_id:親ID password_hash:認証⽤ credential_id:Passkey ID account_id:親ID public_key:公開鍵 account_id:親ID token_digest:トークン expires_at:期限 Passkey移⾏ パスワード認証情報を残したまま、同じAccountへPasskey認証情報を追加 13

Slide 14

Slide 14 text

登録‧認証途中に使う⼀時テーブル Passkeyの登録‧認証開始時にChallengeを有効期限付きで保存し、 完了時に照合‧使⽤済みにして、期限切れの要求や同じ要求の再利⽤を拒否する passkey_registration_ challenges passkey_authentication_ challenges passkey_account_ registrations 既存Accountへ Passkeyを追加 Passkeyでログイン Passkeyで 新規Accountを作成 主なカラム id:登録処理ID account_id:対象Account challenge:要求照合値 expires_at:有効期限 consumed_at:使⽤済み⽇時 DB外部キーあり account_id:accounts.id(FK) 主なカラム id:認証処理ID challenge:要求照合値 expires_at:有効期限 consumed_at:使⽤済み⽇時 認証完了APIの⼊⼒(FKなし) Challenge ID:本テーブルの id Credential ID:passkey_credentials 主なカラム id:登録処理ID prospective_account_id:予定ID email / display_name:作成情報 challenge:照合値 completed_at:完了⽇時 DB外部キーなし prospective_account_id を accounts.id として新規作成 14

Slide 15

Slide 15 text

既存ユーザーのPasskey移⾏と新規登録 ① 既存ユーザーのPasskey移⾏ ② Passkeyの新規登録 従来の⽅式:⼊⼒フォーム ⼊⼒フォーム パスワード認証後、 同じアカウントにPasskeyを追加 メールアドレス‧ 表⽰名を⼊⼒ 作成確認シート Passkeyを作成 新しい⽅式:システムシート Automatic Passkey Upgrade 同じシートで情報選択と作成 ⽒名‧メールアドレスを選択し、 Passkeyを作成 登録済みPasskeyでログイン 移⾏‧新規登録で共通の認証処理 15

Slide 16

Slide 16 text

既存ユーザーのPasskeyへの段階的移⾏ パスワード認証を残してPasskeyを導⼊し、最終的にパスワードレスを⽬指す ① 導⼊前 ② 移⾏期間 ③ ⽬指す状態 パスワードのみ パスワード+Passkey Passkeyのみ パスワードで ログイン Passkeyを追加し、 利⽤を広げる パスワード認証を 廃⽌する 今回の実装範囲(①から②) 今後の移⾏(②から③) ③ パスワード認証を廃⽌する前に ● Passkeyでログインできることを確認する ● Passkeyを利⽤できなくなった場合の復旧⽅法やサポート体制を⽤意する 16

Slide 17

Slide 17 text

パスワード認証後にPasskeyを追加する流れ iOSアプリ Authenticator(認証器) Server ① パスワード認証 リクエストボディ:メールアドレス‧パスワード アクセストークン(後続APIの認証に使⽤) ② Passkey登録の開始を要求 認証ヘッダー:アクセストークン Challenge ID(登録ID)‧RP ID(サービスを識別するドメイン名)‧User ID(アカウントID) ユーザー名(表⽰名)‧Challenge(Serverが発⾏する⼀度限りのランダム値) ③ Passkey作成を要求 RP ID‧User ID‧ユーザー名‧Challenge 認証器が鍵ペアを⽣成 Passkey登録データ 公開鍵などを含む登録結果 秘密鍵は認証器が管理し、 Serverへ送信しない ④ 登録完了を要求(認証ヘッダー:アクセストークン) リクエストボディ:Passkey登録データ‧Challenge ID Passkey登録完了 HTTP 204(ボディなし) ⑤ 登録データを検証して保存 公開鍵などを既存アカウントに追加 パスワード認証情報は保持 17

Slide 18

Slide 18 text

パスワード認証後にPasskeyを追加する流れ iOSアプリ Authenticator(認証器) Server ① パスワード認証 リクエストボディ:メールアドレス‧パスワード アクセストークン(後続APIの認証に使⽤) ② Passkey登録の開始を要求 認証ヘッダー:アクセストークン Challenge ID(登録ID)‧RP ID(サービスを識別するドメイン名)‧User ID(アカウントID) ユーザー名(表⽰名)‧Challenge(Serverが発⾏する⼀度限りのランダム値) ③ Passkey作成を要求 RP ID‧User ID‧ユーザー名‧Challenge 認証器が鍵ペアを⽣成 Passkey登録データ 公開鍵などを含む登録結果 秘密鍵は認証器が管理し、 Serverへ送信しない ④ 登録完了を要求(認証ヘッダー:アクセストークン) リクエストボディ:Passkey登録データ‧Challenge ID Passkey登録完了 HTTP 204(ボディなし) ⑤ 登録データを検証して保存 公開鍵などを既存アカウントに追加 パスワード認証情報は保持 18

Slide 19

Slide 19 text

① パスワード認証を要求 iOSアプリ側 メールアドレスとパスワードを送信して認証する PasskeyMigrationInteractor.swift / migrate内の抜粋 19

Slide 20

Slide 20 text

① 保存済みハッシュでパスワードを検証 Server側 メールアドレスと⼀致する認証情報を取得し、 保存済みのパスワードハッシュで検証する PasswordAuthenticationUseCase.swift / authenticate内の抜粋(途中省略) 20

Slide 21

Slide 21 text

① 認証セッションを⽣成して保存 Server側 認証セッションを⽣成し、アクセストークンのハッシュと有効期限を保存する。 PasswordAuthenticationUseCase.swift / authenticate内の抜粋 21

Slide 22

Slide 22 text

① 認証結果を受け取り、保存 iOSアプリ側 Serverが発⾏したアクセストークンと有効期限を、 後続のPasskey登録API⽤に保存する。 PasskeyMigrationInteractor.swift / migrate内の抜粋 22

Slide 23

Slide 23 text

パスワード認証後にPasskeyを追加する流れ iOSアプリ Authenticator(認証器) Server ① パスワード認証 リクエストボディ:メールアドレス‧パスワード アクセストークン(後続APIの認証に使⽤) ② Passkey登録の開始を要求 認証ヘッダー:アクセストークン Challenge ID(登録ID)‧RP ID(サービスを識別するドメイン名)‧User ID(アカウントID) ユーザー名(表⽰名)‧Challenge(Serverが発⾏する⼀度限りのランダム値) ③ Passkey作成を要求 RP ID‧User ID‧ユーザー名‧Challenge 認証器が鍵ペアを⽣成 Passkey登録データ 公開鍵などを含む登録結果 秘密鍵は認証器が管理し、 Serverへ送信しない ④ 登録完了を要求(認証ヘッダー:アクセストークン) リクエストボディ:Passkey登録データ‧Challenge ID Passkey登録完了 HTTP 204(ボディなし) ⑤ 登録データを検証して保存 公開鍵などを既存アカウントに追加 パスワード認証情報は保持 23

Slide 24

Slide 24 text

② 認証情報を付けてPasskey登録を開始 iOSアプリ側 アクセストークンを認証ヘッダーに付け、Passkey登録開始APIを呼ぶ。 PasskeyMigrationInteractor.swift / registerPasskey内の抜粋 24

Slide 25

Slide 25 text

② Passkey登録オプションと管理情報を⽣成 Server側 アカウント情報から、Passkey登録オプション(options)を⽣成する。 登録ID(challengeID)とChallengeの有効期限(expiresAt)も⽤意する。 PasskeyRegistrationStartUseCase.swift / start内の抜粋(途中省略) 25

Slide 26

Slide 26 text

② Challengeの保存と登録オプションの返却 Server側 Challenge‧登録ID(challengeID)‧アカウントID‧有効期限を保存する。 Passkey登録オプション(options)‧登録ID‧Challengeの有効期限を返す。 PasskeyRegistrationStartUseCase.swift / start内の抜粋(途中省略) 26

Slide 27

Slide 27 text

② Passkey作成情報をDataへ変換 iOSアプリ側 AuthenticationServicesの登録APIへバイト列として渡すため、 Challenge‧User IDをBase64URL⽂字列からDataへ戻す。 PasskeyMigrationInteractor.swift / registerPasskey内の抜粋 27

Slide 28

Slide 28 text

パスワード認証後にPasskeyを追加する流れ iOSアプリ Authenticator(認証器) Server ① パスワード認証 リクエストボディ:メールアドレス‧パスワード アクセストークン(後続APIの認証に使⽤) ② Passkey登録の開始を要求 認証ヘッダー:アクセストークン Challenge ID(登録ID)‧RP ID(サービスを識別するドメイン名)‧User ID(アカウントID) ユーザー名(表⽰名)‧Challenge(Serverが発⾏する⼀度限りのランダム値) ③ Passkey作成を要求 RP ID‧User ID‧ユーザー名‧Challenge 認証器が鍵ペアを⽣成 Passkey登録データ 公開鍵などを含む登録結果 秘密鍵は認証器が管理し、 Serverへ送信しない ④ 登録完了を要求(認証ヘッダー:アクセストークン) リクエストボディ:Passkey登録データ‧Challenge ID Passkey登録完了 HTTP 204(ボディなし) ⑤ 登録データを検証して保存 公開鍵などを既存アカウントに追加 パスワード認証情報は保持 28

Slide 29

Slide 29 text

③ Passkey登録リクエストを⽣成 iOSアプリ側 Serverから受け取った情報で、Passkeyの作成要求を⽤意する。 AuthenticationServicesPasskeyCredentialCreator.swift / createCredential内の抜粋 29

Slide 30

Slide 30 text

③ システムへPasskey作成を要求 iOSアプリ側 performRequests()を実⾏し、結果をDelegate経由で受け取る。 AuthenticationServicesPasskeyCredentialCreator.swift / createCredential内の抜粋 30

Slide 31

Slide 31 text

③ DelegateでPasskey登録データを受け取る iOSアプリ側 登録データは、credentialID‧clientDataJSON‧attestationObjectの3つの値。 3つの値をPasskeyCredentialCreationResultにまとめる。 AuthenticationServicesPasskeyCredentialCreator.swift(抜粋) 31

Slide 32

Slide 32 text

パスワード認証後にPasskeyを追加する流れ iOSアプリ Authenticator(認証器) Server ① パスワード認証 リクエストボディ:メールアドレス‧パスワード アクセストークン(後続APIの認証に使⽤) ② Passkey登録の開始を要求 認証ヘッダー:アクセストークン Challenge ID(登録ID)‧RP ID(サービスを識別するドメイン名)‧User ID(アカウントID) ユーザー名(表⽰名)‧Challenge(Serverが発⾏する⼀度限りのランダム値) ③ Passkey作成を要求 RP ID‧User ID‧ユーザー名‧Challenge 認証器が鍵ペアを⽣成 Passkey登録データ 公開鍵などを含む登録結果 秘密鍵は認証器が管理し、 Serverへ送信しない ④ 登録完了を要求(認証ヘッダー:アクセストークン) リクエストボディ:Passkey登録データ‧Challenge ID Passkey登録完了 HTTP 204(ボディなし) ⑤ 登録データを検証して保存 公開鍵などを既存アカウントに追加 パスワード認証情報は保持 32

Slide 33

Slide 33 text

④ Passkey登録完了要求の送信内容を⽤意 iOSアプリ側 アクセストークンを認証ヘッダーに設定する。 登録データとChallenge IDをリクエストボディに設定する。 PasskeyMigrationInteractor.swift / registerPasskey内の抜粋 33

Slide 34

Slide 34 text

④ Passkey登録完了APIを呼び、成功を確認 iOSアプリ側 Passkey登録完了APIを呼び、成功した場合だけ、 登録成功(.registered)を返す。 PasskeyMigrationInteractor.swift / registerPasskey内の抜粋 34

Slide 35

Slide 35 text

④ Passkey登録データを変換し、UseCaseへ渡す Server側 Passkey登録データをclientResponseに変換し、 Challenge ID(登録ID)‧認証済みアカウントのIDとともにUseCaseへ渡す。 PasskeyRegistrationController.swift / finish内の抜粋(途中省略) 35

Slide 36

Slide 36 text

パスワード認証後にPasskeyを追加する流れ iOSアプリ Authenticator(認証器) Server ① パスワード認証 リクエストボディ:メールアドレス‧パスワード アクセストークン(後続APIの認証に使⽤) ② Passkey登録の開始を要求 認証ヘッダー:アクセストークン Challenge ID(登録ID)‧RP ID(サービスを識別するドメイン名)‧User ID(アカウントID) ユーザー名(表⽰名)‧Challenge(Serverが発⾏する⼀度限りのランダム値) ③ Passkey作成を要求 RP ID‧User ID‧ユーザー名‧Challenge 認証器が鍵ペアを⽣成 Passkey登録データ 公開鍵などを含む登録結果 秘密鍵は認証器が管理し、 Serverへ送信しない ④ 登録完了を要求(認証ヘッダー:アクセストークン) リクエストボディ:Passkey登録データ‧Challenge ID Passkey登録完了 HTTP 204(ボディなし) ⑤ 登録データを検証して保存 公開鍵などを既存アカウントに追加 パスワード認証情報は保持 36

Slide 37

Slide 37 text

⑤ 使⽤可能なChallengeを⼀度だけ消費 Server側 Challenge ID(登録ID)‧紐づくアカウント‧Challengeの有効期限を確認し、 未使⽤かつ期限内のChallengeだけを使⽤済みにする。 PasskeyRegistrationFinishUseCase.swift / finish内の抜粋(途中省略) 37

Slide 38

Slide 38 text

⑤ Passkey登録データを検証し、認証情報を保存 Server側 VerifierでPasskey登録データを検証し、Passkey認証情報をDBへ保存する。 既存のパスワード認証情報は保持する。 PasskeyRegistrationFinishUseCase.swift / finish内の抜粋(途中省略) 38

Slide 39

Slide 39 text

⑤ 検証‧保存後にHTTP 204を返す Server側 UseCaseでPasskey登録データの検証とPasskey認証情報の保存に成功したら、 HTTP 204(ボディなし)を返す。 PasskeyRegistrationController.swift / finish内の抜粋(途中省略) 39

Slide 40

Slide 40 text

Automatic Passkey Upgradeの注意点 ⚠ パスワード認証に成功しても、 Passkeyの登録が完了するとは限らない Passkey登録に失敗しても、 パスワード認証のログイン状態を維持する 40

Slide 41

Slide 41 text

Automatic Passkey Upgradeの成功例 ①「パスワード」 をタップ ②「パスワード」 アプリを選ぶ ③ ログインする アカウントを選ぶ 保存済みパスワードを選び、⾃動⼊⼒でログインする 対応する認証情報管理アプリを使う(例:Appleの「パスワード」) ④ パスワードを 1回だけ使⽤する ⑤ Passkeyの 作成通知が届く 41

Slide 42

Slide 42 text

Automatic Passkey Upgradeの失敗例 ① メールアドレス‧ パスワードを⼿⼊⼒ ②「認証してPasskey登録へ」 をタップ 認証情報管理アプリが確認する主な条件(Apple WWDC24) 直前に、同じアカウントの保存済みパスワードを⾃動⼊⼒したか ⼿⼊⼒だけではこの条件を満たせない(リクエスト⾃体は実⾏する) ⾃動⼊⼒を促す導線を⽤意し、作成されなくてもログインを維持する ③ パスワード認証は成功 Passkey登録は未完了 42

Slide 43

Slide 43 text

実装中に遭遇したエラー 端末ではPasskeyを作成できているのに、 Serverで登録データの検証に失敗する Serverの実⾏ログ(抜粋‧改⾏調整) [PasskeyRegistration] step=registration.verifyCredential event=failed reason=userPresentFlagNotSet errorType=WebAuthn.WebAuthnError [ WARNING ] Abort.400: Passkey登録データを検証できませんでした。 エラーの意味:登録データ内のUPフラグが0(未セット) UP(User Presence)は、登録‧認証時に認証器が利⽤者の操作を確認したかを表す。 利⽤しているswift-webauthnがUP=1を必須としていたため、検証に失敗していた。 43

Slide 44

Slide 44 text

Passkey登録時のUP検証 WebAuthn Level 3 §7.1 ⼿順15(要約) WebAuthnの登録要求で mediation が "conditional" ではない場合、 Serverは登録データのUP=1を確認する。 通常のPasskey登録 Automatic Passkey Upgrade iOS:requestStyle: .standard iOS:requestStyle: .conditional UP=1を確認する UP=1は必須ではない Automatic Passkey Upgradeでは、 UP=0だけを理由に登録データを拒否しない。 WWDC2024 パスキーによるアップグレードと認証情報マネージャによるサインインの効率化 44

Slide 45

Slide 45 text

UP検証エラーへの対応 swift-webauthnをローカルにクローンして修正 swift-webauthnのPR #118と同様に requireUserPresence を追加し、 Challenge‧Origin‧RP IDなどの検証を維持しながら、 Passkey登録時にUP=1を必須にするかを切り替える。 Passkey登録の経路 requireUserPresence とUP検証 通常のPasskey登録 true:UP=1を必須にする Automatic Passkey Upgrade⽤API (パスワード認証済み) false:UP=1を必須にしない SPMの依存先にローカル修正版を指定する(Package.swift) .package(path: "Vendor/swift-webauthn"), 45

Slide 46

Slide 46 text

Passkeyの新規登録 従来の⽅式:⼊⼒フォーム 新しい⽅式:システムシート ASAuthorizationPlatformPublicKeyCredentialProvider Passkey対応:iOS 16以降 ASAuthorizationAccountCreationProvider iOS 26以降 情報をフォームに⼊⼒ Passkeyを作成 ⽒名‧メールアドレスを選択し、 「登録」をタップして作成 46

Slide 47

Slide 47 text

Passkey新規登録でのiOSアプリとServerの役割 従来の⽅式:⼊⼒フォーム ASAuthorizationPlatformPublicKeyCredentialProvider Passkey対応:iOS 16以降 ⼊⼒フォーム 作成確認シート メールアドレス‧ 表⽰名を⼊⼒ Passkeyを作成 Server 登録データを検証して アカウントを作成 新しい⽅式:システムシート ASAuthorizationAccountCreationProvider iOS 26以降 システムシート ⽒名‧メールアドレスを選択し、Passkeyを作成 Server 登録データを検証して アカウントを作成 どちらの⽅式でも、Serverが登録データを検証し、アカウントを作成する iOSアプリがPasskeyの登録データをServerへ送信する 47

Slide 48

Slide 48 text

メールアドレス‧表⽰名の取得タイミング ⽅式 iOSアプリが 情報を取得するタイミング Serverへ 情報を送信するタイミング 従来の⽅式 ⼊⼒フォーム Passkeyの作成前に フォームから取得 開始時‧完了時の どちらでも送信できる 新しい⽅式 システムシート Passkeyの作成完了時に シートの選択結果から取得 選択結果を取得した後に送信 本サンプルでは、フォームのメールアドレス‧表⽰名を開始APIで送信 システムシートの選択結果は、Passkey作成完了時にiOSアプリが受け取る 48

Slide 49

Slide 49 text

システムシートを使ったPasskey新規登録の流れ iOSアプリ システムシート Server ① Passkey新規登録を開始(アカウント情報はまだ送らない) 登録ID(新規登録の管理ID)‧RP ID(サービスを識別するドメイン名) User ID(アカウントID)‧有効期限(登録を完了できる期限) Challenge(Serverが発⾏する⼀度限りのランダム値) ② システムシートを要求 RP ID‧Challenge‧User ID メールアドレス‧⽒名 Passkey登録データ (公開鍵などを含む登録結果) メールアドレス‧⽒名の選択 Passkeyを作成 ③ Passkey新規登録を完了 リクエストボディ:登録ID‧メールアドレス‧表⽰名‧Passkey登録データ ④ 登録データを検証してDBに保存 HTTP 201(ボディなし) アカウント‧メールアドレス‧Passkey 49

Slide 50

Slide 50 text

Passkey登録の仕組み 公開鍵とCredential ID(認証情報のID)を、アカウントに紐づけて保存する iOSアプリ Authenticator(認証器) サービスのServer ① 登録に使う情報を渡す Challenge(Serverが発⾏する⼀度限りのランダム値) RP ID(サービスを識別するドメイン名)‧User ID(アカウントID)など ② Passkeyの作成を要求 鍵ペアを⽣成 登録データ 公開鍵‧Credential IDなど 秘密鍵は認証器が管理 公開鍵を登録データに含める ③ 登録データをServerへ送信 公開鍵‧Credential ID‧Challenge‧Origin(要求元)など 秘密鍵は、サービスのServerへ 送らない ④ 登録データを検証 Challenge‧Origin‧RP IDなど 公開鍵‧Credential IDを アカウントに紐づけて保存 50

Slide 51

Slide 51 text

① Passkey新規登録を開始する iOSアプリ側 registration = try await apiClient.execute( PasskeyAccountCreationStartOperation() ) let options = try PasskeyAccountCreationOptions( relyingPartyID: registration.relyingPartyID, challenge: Base64URLCodec.decode(registration.challenge), userID: Base64URLCodec.decode(registration.userID) ) メールアドレスを選ぶ前に、ServerからPasskey作成⽤の情報を取得する。 Challenge‧User IDは、システムAPIが要求するData型へ変換する。 PasskeyAccountRegistrationInteractor.swift / 抜粋 51

Slide 52

Slide 52 text

① 新規登録の⼀時情報を保存する Server側 let accountID = AccountID() let options = try accountCreationOptionsGenerator .generate(for: accountID) let registrationID = PasskeyAccountRegistrationID() let expiresAt = Date.now.addingTimeInterval(registrationLifetime) try await registrationRepository.create( PasskeyAccountRegistration( id: registrationID, accountID: accountID, email: nil, displayName: nil, challenge: options.challenge, expiresAt: expiresAt, completedAt: nil ) ) 登録ID‧アカウントID‧Challenge‧新規登録の有効期限を⼀時保存する。 メールアドレス‧表⽰名は未選択。アカウントはまだDBに保存しない。 PasskeyAccountRegistrationStartUseCase.swift / 抜粋 52

Slide 53

Slide 53 text

Passkey登録の仕組み 公開鍵とCredential ID(認証情報のID)を、アカウントに紐づけて保存する iOSアプリ Authenticator(認証器) サービスのServer ① 登録に使う情報を渡す Challenge(Serverが発⾏する⼀度限りのランダム値) RP ID(サービスを識別するドメイン名)‧User ID(アカウントID)など ② Passkeyの作成を要求 鍵ペアを⽣成 登録データ 公開鍵‧Credential IDなど 秘密鍵は認証器が管理 公開鍵を登録データに含める ③ 登録データをServerへ送信 公開鍵‧Credential ID‧Challenge‧Origin(要求元)など 秘密鍵は、サービスのServerへ 送らない ④ 登録データを検証 Challenge‧Origin‧RP IDなど 公開鍵‧Credential IDを アカウントに紐づけて保存 53

Slide 54

Slide 54 text

② システムシートでの新規登録を要求する iOSアプリ側 let provider = ASAuthorizationAccountCreationProvider() let request = provider .createPlatformPublicKeyCredentialRegistrationRequest( acceptedContactIdentifiers: [.email], shouldRequestName: true, relyingPartyIdentifier: options.relyingPartyID, challenge: options.challenge, userID: options.userID ) let authorizationController = ASAuthorizationController( authorizationRequests: [request] ) authorizationController.delegate = self // Presentation AnchorとContinuationの設定は省略 authorizationController.performRequests() メールアドレスと⽒名を要求し、同じシートでPasskeyを作成する。 Serverが返したRP ID‧Challenge‧User IDを渡す。 AuthenticationServicesAccountCreationRequester.swift / 抜粋 54

Slide 55

Slide 55 text

② シートの選択内容とPasskey登録データを受け取る iOSアプリ側 func authorizationController( controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization ) { // Controllerの一致確認は省略 guard let accountCreation = authorization.credential as? ASAuthorizationAccountCreationPlatformPublicKeyCredential else { /* エラー処理は省略 */ return } guard case let .email(email) = accountCreation.contactIdentifier, !email.value.isEmpty else { /* エラー処理は省略 */ return } let credential = accountCreation.credentialRegistration // accountCreation.nameから表示名を生成する処理は省略 // 選択内容と登録データをまとめ、待機中のInteractorへ返す } Delegateの成功通知から、メールアドレス‧⽒名‧Passkey登録データを取得する。 contactIdentifierが連絡先、nameが⽒名、credentialRegistrationが登録データ。 AuthenticationServicesAccountCreationRequester.swift / 抜粋 55

Slide 56

Slide 56 text

Passkey登録の仕組み 公開鍵とCredential ID(認証情報のID)を、アカウントに紐づけて保存する iOSアプリ Authenticator(認証器) サービスのServer ① 登録に使う情報を渡す Challenge(Serverが発⾏する⼀度限りのランダム値) RP ID(サービスを識別するドメイン名)‧User ID(アカウントID)など ② Passkeyの作成を要求 鍵ペアを⽣成 登録データ 公開鍵‧Credential IDなど 秘密鍵は認証器が管理 公開鍵を登録データに含める ③ 登録データをServerへ送信 公開鍵‧Credential ID‧Challenge‧Origin(要求元)など 秘密鍵は、サービスのServerへ 送らない ④ 登録データを検証 Challenge‧Origin‧RP IDなど 公開鍵‧Credential IDを アカウントに紐づけて保存 56

Slide 57

Slide 57 text

③ Passkey新規登録の完了APIを呼ぶ iOSアプリ側 let credential = result.credential let operation = PasskeyAccountCreationFinishOperation( registrationID: registration.registrationID, emailAddress: result.emailAddress, displayName: result.displayName, credentialID: Base64URLCodec.encode(credential.credentialID), clientDataJSON: Base64URLCodec.encode(credential.clientDataJSON), attestationObject: Base64URLCodec .encode(credential.attestationObject) ) // キャンセル確認とエラー処理は省略 try await apiClient.execute(operation) シートで受け取ったメールアドレス‧表⽰名とPasskey登録データを、 開始APIで取得した登録IDとともにServerへ送信する。 PasskeyAccountRegistrationInteractor.swift / 抜粋‧改⾏調整 57

Slide 58

Slide 58 text

Passkey登録の仕組み 公開鍵とCredential ID(認証情報のID)を、アカウントに紐づけて保存する iOSアプリ Authenticator(認証器) サービスのServer ① 登録に使う情報を渡す Challenge(Serverが発⾏する⼀度限りのランダム値) RP ID(サービスを識別するドメイン名)‧User ID(アカウントID)など ② Passkeyの作成を要求 鍵ペアを⽣成 登録データ 公開鍵‧Credential IDなど 秘密鍵は認証器が管理 公開鍵を登録データに含める ③ 登録データをServerへ送信 公開鍵‧Credential ID‧Challenge‧Origin(要求元)など 秘密鍵は、サービスのServerへ 送らない ④ 登録データを検証 Challenge‧Origin‧RP IDなど 公開鍵‧Credential IDを アカウントに紐づけて保存 58

Slide 59

Slide 59 text

④ Passkey登録データを検証する Server側 guard let registration = try await registrationRepository.findUsable( by: registrationID, at: Date.now ) else { throw PasskeyAccountRegistrationError.registrationUnavailable } // メールアドレス・表示名の確定、重複チェックは省略 credential = try await registrationVerifier.verify( response, against: registration.challenge, for: registration.accountID ) 有効な登録IDに対応するChallengeとアカウントIDで、登録データを検証する。 ⼊⼒フォーム⽅式と共通のVerifierを使い、新規登録ではUP=1も確認する。 PasskeyAccountRegistrationFinishUseCase.swift / 抜粋 59

Slide 60

Slide 60 text

④ アカウントとPasskeyをまとめて保存する Server側 return try await database.transaction { database in // 登録ID・アカウントID・未完了・有効期限を確認し、 // 新規登録の一時情報を完了済みに更新する処理は省略 try await AccountRecord(account: account) .create(on: database) try await AccountEmailRecord(accountEmail: accountEmail) .create(on: database) try await PasskeyCredentialRecord(credential: credential) .create(on: database) } return true アカウント‧メールアドレス‧Passkeyを同じトランザクションで保存する。 成功後、ControllerはHTTP 201を返す。パスワード認証情報は作成しない。 FluentPasskeyAccountRegistrationRepository.swift / 抜粋 60

Slide 61

Slide 61 text

Passkeyを利⽤したログイン 既存ユーザーの移⾏ 既存アカウントに Passkeyを追加 Passkeyでログイン 作成したPasskeyで ログイン Passkeyで新規登録 同じログイン画⾯‧ 認証処理を利⽤ 新しいアカウントと Passkeyを作成 移⾏‧新規登録のどちらでも、ログインの認証処理は共通 61

Slide 62

Slide 62 text

Passkeyでログインする流れ iOSアプリ Authenticator(認証器) Server ① Passkey認証を開始(アカウントはまだ指定しない) Challenge ID(今回の認証を識別するID)‧RP ID(サービスを識別するドメイン名) Challenge(Serverが発⾏する⼀度限りのランダム値) 有効期限(Challengeを使⽤できる期限) ② Passkey認証を要求 RP ID‧Challenge 署名付き認証データ(Assertion) Credential ID(認証情報のID) 署名など Passkeyを選択‧本⼈確認 登録済みの秘密鍵で署名 ③ 署名付き認証データを送信 Challenge ID‧Credential ID‧署名など ⑤ アクセストークン(API認証⽤) 有効期限とともに受け取り、端末に保存 ④ 署名付き認証データを検証 ⑤ 認証セッション(ログイン状態)を発⾏ 62

Slide 63

Slide 63 text

Passkey認証の仕組み 登録済みの公開鍵で署名を検証し、ログインを許可する iOSアプリ Authenticator(認証器) サービスのServer ① 認証に使う情報を渡す Challenge(Serverが発⾏する⼀度限りのランダム値) ② 利⽤者の確認‧署名を要求 署名付き認証データ(Assertion) Credential ID(認証情報のID) 署名など 登録済みの秘密鍵を使⽤ Face ID‧PINで本⼈確認 署名を⽣成 ③ 署名付き認証データをServerへ送信 Credential ID‧署名‧Challenge‧Origin(要求元)など 新しい鍵は作らず、 登録済みの鍵を利⽤する ⑤ アクセストークンを返す (ログイン状態を⽰す値) ④ 署名付き認証データを検証 登録済みの公開鍵を使⽤ 認証セッション(ログイン状態)を発⾏ 63

Slide 64

Slide 64 text

① Passkey認証を開始する iOSアプリ側 let startResponse = try await apiClient.execute( PasskeyAuthenticationStartOperation() ) let options = try PasskeyAuthenticationOptions( relyingPartyID: startResponse.relyingPartyID, challenge: Base64URLCodec.decode(startResponse.challenge) ) let assertion = try await assertionRequestor.requestAssertion( using: options ) アカウントを指定せずに認証開始APIを呼び、RP IDとChallengeを取得する。 ChallengeをDataへ戻し、システムへの認証要求に渡す。 PasskeyAuthenticationInteractor.swift / 抜粋‧改⾏調整 64

Slide 65

Slide 65 text

① 認証⽤のChallengeを発⾏する Server側 let options = try optionsGenerator.generate() let challengeID = PasskeyChallengeID(value: UUID()) let expiresAt = Date.now.addingTimeInterval(challengeLifetime) try await challengeRepository.create( id: challengeID, challenge: options.challenge, expiresAt: expiresAt ) return PasskeyAuthenticationStartResult( challengeID: challengeID, options: options, expiresAt: expiresAt ) 今回の認証⽤のChallengeを、Challenge ID‧有効期限とともに保存する。 保存に成功してから、同じChallengeをiOSアプリへ返す。 PasskeyAuthenticationStartUseCase.swift / 抜粋‧改⾏調整 65

Slide 66

Slide 66 text

Passkey認証の仕組み 登録済みの公開鍵で署名を検証し、ログインを許可する iOSアプリ Authenticator(認証器) サービスのServer ① 認証に使う情報を渡す Challenge(Serverが発⾏する⼀度限りのランダム値) ② 利⽤者の確認‧署名を要求 署名付き認証データ(Assertion) Credential ID(認証情報のID) 署名など 登録済みの秘密鍵を使⽤ Face ID‧PINで本⼈確認 署名を⽣成 ③ 署名付き認証データをServerへ送信 Credential ID‧署名‧Challenge‧Origin(要求元)など 新しい鍵は作らず、 登録済みの鍵を利⽤する ⑤ アクセストークンを返す (ログイン状態を⽰す値) ④ 署名付き認証データを検証 登録済みの公開鍵を使⽤ 認証セッション(ログイン状態)を発⾏ 66

Slide 67

Slide 67 text

② システムへPasskey認証を要求する iOSアプリ側 let provider = ASAuthorizationPlatformPublicKeyCredentialProvider( relyingPartyIdentifier: options.relyingPartyID ) let request = provider.createCredentialAssertionRequest( challenge: options.challenge ) request.userVerificationPreference = .required request.allowedCredentials = [] let controller = ASAuthorizationController( authorizationRequests: [request] ) controller.delegate = self // 表示先・Continuation・キャンセル処理の設定は省略 controller.performRequests() Credential IDを事前に限定せず、利⽤者が登録済みPasskeyを選択する。 本⼈確認を要求し、署名付き認証データをDelegateで受け取る。 AuthenticationServicesPasskeyAssertionRequester.swift / 抜粋‧改⾏調整 67

Slide 68

Slide 68 text

② Delegateで署名付き認証データを受け取る iOSアプリ側 guard let credential = authorization.credential as? ASAuthorizationPlatformPublicKeyCredentialAssertion else { /* エラー処理は省略 */ return } // Controllerの一致確認・userIDの必須チェックは省略 let result = PasskeyAssertionResult( credentialID: credential.credentialID, clientDataJSON: credential.rawClientDataJSON, authenticatorData: credential.rawAuthenticatorData, signature: credential.signature, userHandle: credential.userID ) finish(returning: result) Credential ID‧Client Data JSON‧Authenticator Data‧署名‧User Handleを取得する。 端末で認証データを取得後、Serverによる検証へ進む。 AuthenticationServicesPasskeyAssertionRequester.swift / 抜粋‧改⾏調整 68

Slide 69

Slide 69 text

Passkey認証の仕組み 登録済みの公開鍵で署名を検証し、ログインを許可する iOSアプリ Authenticator(認証器) サービスのServer ① 認証に使う情報を渡す Challenge(Serverが発⾏する⼀度限りのランダム値) ② 利⽤者の確認‧署名を要求 署名付き認証データ(Assertion) Credential ID(認証情報のID) 署名など 登録済みの秘密鍵を使⽤ Face ID‧PINで本⼈確認 署名を⽣成 ③ 署名付き認証データをServerへ送信 Credential ID‧署名‧Challenge‧Origin(要求元)など 新しい鍵は作らず、 登録済みの鍵を利⽤する ⑤ アクセストークンを返す (ログイン状態を⽰す値) ④ 署名付き認証データを検証 登録済みの公開鍵を使⽤ 認証セッション(ログイン状態)を発⾏ 69

Slide 70

Slide 70 text

③ 署名付き認証データをServerへ送信する iOSアプリ側 response = try await apiClient.execute( PasskeyAuthenticationFinishOperation( challengeID: startResponse.challengeID, credentialID: Base64URLCodec .encode(assertion.credentialID), clientDataJSON: Base64URLCodec .encode(assertion.clientDataJSON), authenticatorData: Base64URLCodec .encode(assertion.authenticatorData), signature: Base64URLCodec .encode(assertion.signature), userHandle: Base64URLCodec .encode(assertion.userHandle) ) ) 署名付き認証データをBase64URLへ変換し、Challenge IDとともに認証完了APIへ送る。 署名検証に使うClient Data JSONは、再構築せず元のバイト列を渡す。 PasskeyAuthenticationInteractor.swift / 抜粋‧改⾏調整 70

Slide 71

Slide 71 text

Passkey認証の仕組み 登録済みの公開鍵で署名を検証し、ログインを許可する iOSアプリ Authenticator(認証器) サービスのServer ① 認証に使う情報を渡す Challenge(Serverが発⾏する⼀度限りのランダム値) ② 利⽤者の確認‧署名を要求 署名付き認証データ(Assertion) Credential ID(認証情報のID) 署名など 登録済みの秘密鍵を使⽤ Face ID‧PINで本⼈確認 署名を⽣成 ③ 署名付き認証データをServerへ送信 Credential ID‧署名‧Challenge‧Origin(要求元)など 新しい鍵は作らず、 登録済みの鍵を利⽤する ⑤ アクセストークンを返す (ログイン状態を⽰す値) ④ 署名付き認証データを検証 登録済みの公開鍵を使⽤ 認証セッション(ログイン状態)を発⾏ 71

Slide 72

Slide 72 text

④ Challengeと登録済み認証情報を取得する Server側 guard let challenge = try await challengeRepository.consume( id: challengeID, at: Date.now ) else { throw PasskeyAuthenticationFinishError.challengeUnavailable } guard let credential = try await credentialRepository .find(by: response.credentialID), let account = try await accountRepository .find(by: credential.accountID) else { throw PasskeyAuthenticationFinishError.invalidCredentials } 未使⽤かつ期限内のChallengeを、⼀度だけ消費する。 Credential IDから登録済みの公開鍵と所有アカウントを取得する。 PasskeyAuthenticationFinishUseCase.swift / 抜粋‧改⾏調整 72

Slide 73

Slide 73 text

④ 署名付き認証データを検証し、認証状態を更新する Server側 verifiedCredential = try await authenticationVerifier.verify( response, against: challenge, using: credential ) // 検証エラーの処理・キャンセル確認は省略 guard try await credentialRepository .update(verifiedCredential) else { throw PasskeyAuthenticationFinishError.invalidCredentials } 保存済みの公開鍵で署名を検証し、Challenge‧Origin‧RP IDなどを確認する。 検証後の署名回数とバックアップ状態を保存する。 PasskeyAuthenticationFinishUseCase.swift / 抜粋‧改⾏調整 73

Slide 74

Slide 74 text

Passkey認証の仕組み 登録済みの公開鍵で署名を検証し、ログインを許可する iOSアプリ Authenticator(認証器) サービスのServer ① 認証に使う情報を渡す Challenge(Serverが発⾏する⼀度限りのランダム値) ② 利⽤者の確認‧署名を要求 署名付き認証データ(Assertion) Credential ID(認証情報のID) 署名など 登録済みの秘密鍵を使⽤ Face ID‧PINで本⼈確認 署名を⽣成 ③ 署名付き認証データをServerへ送信 Credential ID‧署名‧Challenge‧Origin(要求元)など 新しい鍵は作らず、 登録済みの鍵を利⽤する ⑤ アクセストークンを返す (ログイン状態を⽰す値) ④ 署名付き認証データを検証 登録済みの公開鍵を使⽤ 認証セッション(ログイン状態)を発⾏ 74

Slide 75

Slide 75 text

⑤ 認証セッションを発⾏する Server側 let token = try sessionTokenGenerator.generate() let tokenDigest = sessionTokenHasher.hash(token) let expiresAt = Date.now.addingTimeInterval(sessionLifetime) let session = AuthenticationSession( id: AuthenticationSessionID(), accountID: account.id, tokenDigest: tokenDigest, expiresAt: expiresAt, revokedAt: nil ) try await sessionRepository.create(session) return PasskeyAuthenticationFinishResult( account: account, token: token, expiresAt: expiresAt ) 認証セッションを作成し、アクセストークンのハッシュをDBへ保存する。 保存後、アクセストークンと有効期限をiOSアプリへ返す。 PasskeyAuthenticationFinishUseCase.swift / 抜粋‧改⾏調整 75

Slide 76

Slide 76 text

⑤ 認証情報を保存し、ログインを完了する iOSアプリ側 let credential = AuthorizationCredential( accessToken: response.accessToken, expiresAt: response.expiresAt ) guard credential.isUsable(at: Date.now) else { throw PasskeyAuthenticationError .invalidAuthorizationCredential } try await credentialStore.save(credential) authenticatedCredential = credential return response.displayName アクセストークンと有効期限を、Keychainを使うStoreへ保存する。 保存に成功してから、画⾯へログイン成功を返す。 PasskeyAuthenticationInteractor.swift / 抜粋‧改⾏調整 76

Slide 77

Slide 77 text

まとめ ● パスワード認証を維持しながら、Passkeyへ段階的に移⾏する 認証済みのアカウントに追加し、追加に失敗してもログイン状態を維持する。 ● Automatic Passkey Upgradeでは、⾃動⼊⼒の導線とUP検証を整える 対応する認証情報管理アプリで、保存済みパスワードの⾃動⼊⼒を促す。 ⾃動移⾏の登録データでは、ServerでUP=1を必須にしない。 ● 新規登録では、iOSアプリが情報を取得するタイミングが異なる フォームの⼊⼒値は、開始時にも完了時にもServerへ送信できる。 システムシートの選択結果は、Passkey作成完了時に受け取る。 ● 登録経路が異なっても、ログインの認証処理は共通にできる Serverが登録済みの公開鍵で署名付き認証データを検証し、認証セッションを発⾏する。 77

Slide 78

Slide 78 text

参考資料 基礎 ● Meet passkeys — WWDC22∕仕組み‧基本概念 iOS実装 ● Supporting passkeys ● Streamline sign-in with passkey upgrades and credential managers — 登録‧ログインの実装 WWDC24∕Automatic Passkey Upgrade ● What’s new in passkeys ● Performing fast account creation with passkeys — WWDC25∕iOS 26の新規登録 — 新規登録の公式サンプル Server実装‧仕様 ● Web Authentication: Level 3 ● Going passwordless with Passkeys ● swift-webauthn PR #118 — 登録‧認証‧UPの検証条件 — Swift.org∕Server実装 — requireUserPresenceの追加 78

Slide 79

Slide 79 text

Thank you for listening !

Slide 80

Slide 80 text

参考資料

Slide 81

Slide 81 text

Challengeが必要な理由 Challenge:Serverが認証ごとに発⾏する、予測困難なランダム値 過去の署名付き認証データを再送してログインする 「リプレイ攻撃」を防ぐ。 毎回同じChallengeの場合 毎回異なるChallengeの場合 過去のデータ:Challenge = A 今回の要求:Challenge = A 過去のデータ:Challenge = A 今回の要求:Challenge = B 署名検証だけでは 過去の再送と区別できない 今回の値と⼀致しないため、 Serverが再送を拒否する 認証時はChallengeを含むデータも署名の対象。 AをBに書き換えても、秘密鍵なしでは有効な署名を作り直せない。 出典:W3C WebAuthn Level 3 Challenge∕認証データの検証 81

Slide 82

Slide 82 text

Passkey登録で受け渡すデータ 利⽤する場⾯ データ名 ⽣成元 内容‧Serverでの⽤途 Passkey登録開始 Passkey登録完了 Challenge ID (登録ID) Server Passkey登録開始時に発⾏するID。 Passkey登録完了要求でServerへ返し、 保存した登録⼿続きを特定する。 Passkey作成 Passkey登録完了 Credential ID (認証情報のID) 認証器 作成した認証情報を識別するID。 公開鍵とともにアカウントへ保存。 Passkey作成 Passkey登録完了 Client Data JSON (登録要求の情報) iOSのシステム処理 AuthenticationServices Challenge‧Origin(要求元)を 含むJSON。Serverが値を照合する。 Passkey作成 Passkey登録完了 Attestation Object (認証器が⽣成) 認証器 主な内容:公開鍵‧Credential ID、 RP IDのハッシュ‧本⼈確認の実施状況。 Serverが検証し、認証情報を取り出す。 補⾜ Challenge ID:Passkey登録開始と登録完了を対応づける、本サンプルAPI独⾃の管理ID。 Challenge:Serverが要求ごとに発⾏する、⼀度限りのランダム値。 82

Slide 83

Slide 83 text

Passkey登録の主な検証項⽬ iOSアプリが送信する登録データ ① 登録条件 を確認 Passkey登録⽤データ ② 発⾏した値 などと照合 Challenge 確認 利⽤者の操作‧本⼈確認の結果 ⼀致確認 ⼀致確認 https://example.com 許可するOrigin https://example.com RP(Relying Party) Passkeyで認証するサービス 認証器が利⽤したRP IDを SHA-256でハッシュ化 ⼀致確認 公開鍵‧Credential ID 確認 認証器が新しく⽣成 今回発⾏したChallenge Serverに保存した値 RP IDのハッシュ ③ 保存する 認証情報を確認 登録開始時の要求条件 データ形式と本⼈確認を検証 Serverが発⾏した値を返す Origin(クライアントが設定) 値の照合 Serverが検証に使う情報 Serverが設定したRP ID SHA-256("example.com") 公開鍵の形式‧暗号⽅式 Credential IDの重複も確認 83

Slide 84

Slide 84 text

Passkey認証の主な検証項⽬ iOSアプリが送信する認証データ ① 認証情報 を特定 Credential ID: key-A ② 認証データ を検証 Challenge IDで検索 認証情報を識別するID ⼀致確認 ⼀致確認 署名と検証に必要なデータ Challenge‧Origin‧ RP IDのハッシュなどを含む 許可するOrigin https://example.com RP(Relying Party) RP IDのハッシュ 署名の検証 今回発⾏したChallenge Serverに保存した値 https://example.com 認証器が利⽤したRP IDを SHA-256でハッシュ化 ID: key-A Account A / 公開鍵 A Serverが発⾏した値を返す Origin(クライアントが設定) 値の照合 Serverが検証に使う情報 Passkeyで認証するサービス ⼀致確認 Serverが設定したRP ID SHA-256("example.com") 検証 ①で特定した公開鍵 A 署名の正しさを検証 84