ハイライト
Apple の Authenticated Experience チームの Garrett が、FIDO Alliance 標準に基づく次世代認証テクノロジーである Passkey について包括的に紹介します。Passkey は、macOS Ventura と iOS 16 のすべてのユーザーが正式に利用できるようになりました (昨年は開発者プレビューでした)。
主要内容
パスワード ログインに関しては、長年にわたって問題が 2 つあります。ユーザーがアカウントごとに強力なパスワードを作成するのは難しく、サーバーはパスワードの派生データを保存する必要があります。アプリや Web サイトが適切なプロセスを実行したとしても、フィッシング、パスワードの再利用、認証情報の漏洩によってアカウントが侵害される可能性があります (00:33)。
パスキーは、ログイン資格情報をアカウントごとのキーのペアに変更します。デバイスはキー ペアを生成し、公開キーはサーバーに保存され、秘密キーはユーザー デバイスに残ります。ログイン時に、サーバーは 1 回限りのチャレンジを発行し、デバイスは秘密キーを使用してローカルで署名し、サーバーは公開キーを使用して署名を検証します (26:06)。サーバーには漏洩する秘密キーがなく、ユーザーにはフィッシング Web サイトに騙される文字列がありません。
このセッションの焦点は実装です。Apple は、既存のユーザー名入力フィールドと自動入力プロセスにパスキーを入力することを推奨しています。ユーザーに表示されるのは、使い慣れたログイン フォームのままです。アカウントにすでにパスキーがある場合、QuickType バーが直接提案を表示し、クリックして Face ID または Touch ID 経由でログインします (09:15)。
詳細
まずアプリを Web サイトに関連付けます
(11:30) アプリでパスキーを使用する前に、関連ドメイン (関連ドメイン名) を構成し、サイトの Apple App Site Association ファイルで宣言する必要があります。webcredentials仕える。
{
"webcredentials": {
"apps": [ "A1B2C3D4E5.com.example.Shiny" ]
}
}
キーポイント:
webcredentialsこのドメイン名により、アプリがパスワードやパスキーなどの Web サイト資格情報を使用できるようになります。appsこの例では、配列はチーム ID とバンドル ID に書き込まれます。A1B2C3D4E5.com.example.Shinyシャイニーアプリに対応。- このステップにより、アプリが以下であることをシステムに知らせます。
example.com同じログインドメインに属している。
次に、ユーザー名入力ボックスにマークを付けますtextContentType。システムはこのフラグを使用して、パスキーの提案を表示する場所を決定します (11:47)。
override func viewDidLoad() {
super.viewDidLoad()
//Additional setup…
userNameField.textContentType = .username
}
キーポイント:
viewDidLoad()これはビューが初期化される場所であり、入力ボックスのプロパティを設定するのに適しています。super.viewDidLoad()UIKit のデフォルトの初期化フローを維持します。userNameField.textContentType = .usernameこれがユーザー名フィールドであり、パスキーとパスワードの候補がこの入力場所に表示されることをシステムに伝えます。
オートフィルを使用してパスキー ログインを開始する
(11:59) ネイティブ アプリが合格しましたAuthenticationServices内部ASAuthorizationPlatformPublicKeyCredentialProviderパスキー要求を開始します。Apple は、ユーザーがユーザー名フィールドに注目したときに候補を表示できるように、できるだけ早くオートフィル支援リクエストを有効にすることを推奨しています (13:05)。
// AutoFill-assisted passkey request
func signIn() {
let challenge: Data = … // Fetched from server
let provider =
ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier: "example.com")
let request =
provider.createCredentialAssertionRequest(
challenge: challenge)
let controller =
ASAuthorizationController(
authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
// Start the request
controller.performAutoFillAssistedRequests()
}
キーポイント:
challengeこれはサーバーから取得する必要があり、WebAuthn ログイン要求の 1 つのチャレンジに対応します。ASAuthorizationPlatformPublicKeyCredentialProviderパスキー要求を処理するプロバイダーです。relyingPartyIdentifier: "example.com"パスキーが属するログイン ドメインを指します。createCredentialAssertionRequest(challenge:)ログイン用のアサーション リクエストを作成します。ASAuthorizationController認可リクエストの実行を担当します。delegate成功または失敗のコールバックを受け取ります。presentationContextProvider認証インターフェースをどのウィンドウでハングアップするかをシステムに指示します。performAutoFillAssistedRequests()オートフィルアシストリクエストを開始します。ユーザーが QuickType バーでパスキーを選択すると、システムは Face ID または Touch ID をトリガーします。
成功すると、ユーザー名入力ボックスには何も入力されません。アプリは統合されたASAuthorizationControllerDelegateコールバックしてパスキー アサーションを取得します (13:29)。
// Completing a passkey sign in
func authorizationController(controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization) {
guard let passkeyAssertion = authorization.credential as?
ASAuthorizationPlatformPublicKeyCredentialAssertion
else { … }
let signature = passkeyAssertion.signature
let clientDataJSON = passkeyAssertion.rawClientDataJSON
// Pass these values to your server, and complete the sign in
…
}
キーポイント:
didCompleteWithAuthorization全てですASAuthorization成功後の統一入口。authorization.credential最初に変換する必要がありますASAuthorizationPlatformPublicKeyCredentialAssertion。signatureこれは、チャレンジの秘密キーを使用してデバイスによって生成された署名です。rawClientDataJSONWebAuthn 認証に必要なクライアント データです。- アプリはログインの成功をローカルで判断しませんが、これらの値をサーバーに渡して WebAuthn 検証を完了します。
ユーザーがユーザー名を入力した後、モーダル ログインに切り替えます。
(15:30) ユーザーが自動入力候補をクリックせずにユーザー名を手動で入力した場合、Apple は自動入力要求をキャンセルし、モーダル パスキー ログイン フォームをポップアップすることをお勧めします。コードの変更は非常に小さく、最後のステップを次のように置き換えるだけです。performRequests()。
// Modal passkey request
func signIn() {
let challenge: Data = … // Fetched from server
let provider =
ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier: "example.com")
let request =
provider.createCredentialAssertionRequest(
challenge: challenge)
let controller =
ASAuthorizationController(
authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
// Start the request
controller.performRequests()
}
キーポイント:
- 前半は依然として同じチャレンジ、プロバイダー、およびアサーション リクエストです。
performRequests()使用可能なパスキーをリストするモーダル フォームが表示されます。- このフォームは、近くのデバイスのパスキーへのアクセスも提供します。
- 成功後も同じデリゲート コールバックが使用されるため、バックエンド検証ロジックを再利用できます。
デバイスに複数のパスキーがある場合、入力されたユーザー名に基づいてサーバーから対応する認証情報 ID を見つけて、許可リストを設定できます (20:55)。
// Modal request with allow list
func signIn(userName: String) {
let challenge: Data = … // Fetched from server
let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier:"example.com")
let request = provider.createCredentialAssertionRequest(
challenge: challenge)
let credentialIDs: [Data] = … // Fetched from server for provided userName
request.allowedCredentials = credentialIDs.map(
ASAuthorizationPlatformPublicKeyCredentialDescriptor.init(credentialID:))
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
// Start the request
controller.performRequests()
}
キーポイント:
userNameユーザーが入力したアカウント名です。credentialIDsサーバーから; WebAuthn サーバーはユーザー名で資格情報 ID を見つけられる必要があります。allowedCredentials一致するパスキーのみを表示するようにモーダル フォームを制限します。ASAuthorizationPlatformPublicKeyCredentialDescriptor.init(credentialID:)元の ID をリクエストに必要な記述子に変換します。- このモードは、ユーザーがアカウント コンテキストを提供したシナリオに適しています。
ローカル パスキーがない場合、サイレント フォールバックするかどうかを決定する
(22:56) ネイティブ パスキーがない場合、デフォルトのモーダル リクエストでは QR コードが表示され、ユーザーは近くのデバイスでログインできます。最初にこのマシンに利用可能な資格情報があるかどうかだけをテストしたい場合は、次のように使用できます。.preferImmediatelyAvailableCredentials。一致する資格情報がない場合、システムはエラー コールバックを介してサイレントにフォールバックします。
// Modal passkey request, silent fallback
func signIn() {
let challenge: Data = … // Fetched from server
let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier:"example.com")
let request = provider.createCredentialAssertionRequest(
challenge: challenge)
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
// Start the request
controller.performRequests(options: .preferImmediatelyAvailableCredentials)
}
キーポイント:
performRequests(options:)同じパスキー要求を使用し、表示戦略のみを変更します。.preferImmediatelyAvailableCredentials現在のデバイスですぐに利用できる認証情報を優先するようにシステムに依頼します。- 利用可能な認証情報がない場合、システムはクロスデバイス QR コードを直接表示せず、エラー コールバックを開始します。
- Apple のリマインダー: アプリは、近くのデバイスのログイン入り口にアクセスできるようにするために、オートフィル リクエストまたはデフォルトのモーダル リクエストを他の場所で提供する必要があります (23:48)。
// Handling a silent fallback
func authorizationController(controller: ASAuthorizationController,
didCompleteWithError error: Error) {
guard let error = error as? ASAuthorizationError else { … }
if error.code == .canceled {
// Either the user canceled the sheet, or there were no credentials available.
showSignInForm()
}
}
キーポイント:
didCompleteWithError承認が失敗した場合、またはキャンセルされた場合のデリゲート コールバックです。ASAuthorizationError構造化されたエラー コードを提供します。.canceledソースは 2 つあります。ユーザーがフォームを閉じるか、または.preferImmediatelyAvailableCredentialsネイティブ資格情報が見つかりません。showSignInForm()従来のログイン フォームを表示するためのフォールバック パスです。
Web ターミナルは条件付きメディエーションを使用してオートフィルにアクセスします
(16:53) Web 側は標準の WebAuthn API を使用します。最初のステップは、ユーザー名の入力ボックスで両方を宣言することです。usernameそしてwebauthn自動入力トークン。
<input type="text" id="username-field" autocomplete="username webauthn" >
キーポイント:
autocomplete="username webauthn"ブラウザにユーザー名フィールドにパスワードとパスキーの提案を表示させます。- このマークは、
.usernametext content type。 - これは、自動入力支援 WebAuthn リクエストが正しい場所に表示されるための前提条件です。
次に、条件付きメディエーションが JavaScript で利用できるかどうかを確認し、次を使用します。navigator.credentials.getリクエストを開始します (17:09)。
// AutoFill-assisted WebAuthn request (JavaScript)
function signIn() {
if (!PublicKeyCredential.isConditionalMediationAvailable ||
!PublicKeyCredential.isConditionalMediationAvailable()) {
// Browser doesn't support AutoFill-assisted requests.
return;
}
const options = {
"publicKey": {
challenge: … // Fetched from server
},
mediation: "conditional"
};
navigator.credentials.get(options)
.then(assertion => {
// Pass the assertion to your server.
});
}
キーポイント:
PublicKeyCredential.isConditionalMediationAvailable標準機能検出入口です。- サポートされていないブラウザでのエラー プロセスの開始を回避するために、検出に失敗した場合は直接戻ります。
challengeサーバー側からも。mediation: "conditional"WebAuthn リクエストを自動入力支援モードに切り替えます。navigator.credentials.get(options)返す約束。assertion検証のためにサーバーに送り返す必要があり、ブラウザはログイン結果を保存しません。
モーダル WebAuthn フォームを表示したい場合は、削除してくださいmediation: "conditional"即可(18:14)。
// Modal WebAuthn request (JavaScript)
function signIn() {
var options = {
"publicKey": {
challenge: … // Fetched from server
}
};
navigator.credentials.get(options)
.then(function (assertion) {
// Pass the assertion to your server.
});
}
キーポイント:
options.publicKey.challengeこれは依然としてサーバーによって生成された WebAuthn チャレンジです。- いいえ
mediationパラメーターを指定すると、ブラウザーにモーダル リクエストが表示されます。 - Apple は、ボタンをクリックするなどのユーザーのジェスチャによってモーダル WebAuthn リクエストをトリガーすることを推奨しています (19:25)。
- 返されました
assertionAutoFill プロセスと同様に、サーバー側の検証が返されます。
複数の資格情報を同じ認可リクエストに含めることができます
(24:40) 現実の移行は 1 つのステップで行われるわけではありません。ユーザーは、パスキー、パスワード、Apple でサインインから 1 つ以上の認証情報を持っている場合があります。ASAuthorizationController同じモーダル フォームで複数の資格情報を要求できます。
// Combined credential modal request
func signIn() {
let challenge: Data = … // Fetched from server
let passkeyProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(
relyingPartyIdentifier:"example.com")
let passkeyRequest = passkeyProvider.createCredentialAssertionRequest(
challenge: challenge)
let passwordRequest = ASAuthorizationPasswordProvider().createRequest()
let signInWithAppleRequest = ASAuthorizationAppleIDProvider().createRequest()
let controller = ASAuthorizationController(
authorizationRequests: [passkeyRequest, passwordRequest, signInWithAppleRequest])
controller.delegate = self
controller.presentationContextProvider = self
// Start the request
controller.performRequests()
}
キーポイント:
passkeyRequest既存のパスキー ログインを処理します。ASAuthorizationPasswordProvider().createRequest()保存されたパスワード認証情報を要求します。ASAuthorizationAppleIDProvider().createRequest()Apple 資格情報でサインインを要求します。authorizationRequestsアレイは 3 つのリクエストを同じコントローラーに配信します。- システム フォームには、現在のデバイスで利用可能な認証情報のみが表示されます。
コールバックは、認証情報の種類 (25:02) に応じて、さまざまな処理ロジックに分散する必要があります。
// Completing a combined credential request
func authorizationController(controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization) {
switch authorization.credential {
case let passkeyAssertion as ASAuthorizationPlatformPublicKeyCredentialAssertion:
finishSignIn(with: passkeyAssertion)
case let signInWithAppleCredential as ASAuthorizationAppleIDCredential:
finishSignIn(with: signInWithAppleCredential)
case let passwordCredential as ASPasswordCredential:
finishSignIn(with: passwordCredential)
default:
// Handle other credential types
break
}
}
キーポイント:
switch authorization.credentialランタイム タイプを使用して、ユーザーがどの資格情報を選択したかを判断します。ASAuthorizationPlatformPublicKeyCredentialAssertionパスキー認証を使用します。ASAuthorizationAppleIDCredential「Apple でサインイン」に進みます。ASPasswordCredentialパスワードを使用してログインします。default将来または他の資格情報タイプの処理のためにエントリを予約します。
クロスデバイスログインはシステムによって完了します
(28:33) パスキーは、ネイティブ認証情報を持たないデバイスでのログインをサポートします。クライアントが QR コードを表示し、パスキーを保持しているデバイスが QR コードをスキャンします。その後、2 つのデバイスがローカル キーのネゴシエーションを完了し、Bluetooth 経由で物理的な近接性を証明します。
このプロセスにはいくつかのセキュリティ境界があります。ワンタイム暗号化キーのペアは QR コードにエンコードされます。携帯電話ブロードキャストには中継サーバーのルーティング情報が含まれます。ローカル交換が完了すると、ネットワーク通信はエンドツーエンドで暗号化され、中継サーバーはコンテンツを読み取ることができなくなります。 Web サイトはクロスデバイス通信に参加しません (29:49)。したがって、QR コードを被害者にリモートで送信したり、偽の Web サイトで QR コードを生成したりしても、Bluetooth 近接証明を完了することはできません。
重要ポイント
-
何をすべきか: 既存のログイン ページにパスキーの自動入力を追加します。 実行する価値がある理由: Session では、新しいログイン インターフェイスを作成する代わりに、ユーザー名フィールドにパスキーを入力することを明確に推奨しています。 開始方法: 構成
webcredentials関連付けられたドメイン名、ユーザー名入力ボックスに設定.username、起動するperformAutoFillAssistedRequests()。 -
対処方法: ユーザーがユーザー名を入力すると、対応するアカウントのパスキーのみが表示されます。 実行する価値がある理由: 複数のアカウントがデバイスに保存される場合があります。許可リストを使用すると、間違ったアカウントを選択する可能性を減らすことができます。 開始方法: サーバーはユーザー名に応じて資格情報 ID を返し、クライアントはそれらをユーザー名にマッピングします。
ASAuthorizationPlatformPublicKeyCredentialDescriptorそしてに設定しますrequest.allowedCredentials。 -
内容: アプリが起動すると、従来のログイン フォームを表示する前にネイティブ認証情報が試行されます。 実行する価値がある理由: ローカル認証情報を持つユーザーは、システム フォームに直接入力できます。認証情報を持たないユーザーには最初に QR コードが表示されません。 開始方法: 電話
performRequests(options: .preferImmediatelyAvailableCredentials)、存在する.canceledコールバックでログインフォームを表示します。 -
対処方法: パスキー、パスワード、および「Apple でサインイン」を同じモーダル フォームに入力します。 実行する価値がある理由: 移行期間中のユーザーの資格情報の種類は一貫性がありませんが、統合されたリクエストによりエントリの断片化を軽減できます。 開始方法: 作成する
passkeyRequest、passwordRequest、signInWithAppleRequest、一緒に通過しますASAuthorizationController。 -
対処方法: Web サイトへのログインに条件付きメディエーションを追加します。 実行する価値がある理由: Web クライアントはユーザー名フィールドにパスキーの候補を表示することもできるため、ユーザーは最初に複雑なログイン方法を選択する必要がありません。 開始方法: 入力ボックスを設定します
autocomplete="username webauthn"、 検出するPublicKeyCredential.isConditionalMediationAvailable()、そしてバンドに電話しますmediation: "conditional"のnavigator.credentials.get()。
関連セッション
- Apple でサインインのエクスペリエンスを強化する — こちらも基づいています
ASAuthorization、アカウントの重複排除、資格情報のクエリ、および Apple でサインインのフィールド処理について説明します。 - CAPTCHA をプライベート アクセス トークンに置き換える — 従来の Web セキュリティの課題をデバイスとアカウントの信頼機能に置き換えることについて議論します。
- アプリとサーバーの DNS セキュリティを向上する - ネットワーク層からのフィッシングやハイジャックのリスクを軽減し、パスキー ログインと合わせて検討するのに適しています。
- 管理対象デバイスの構成証明の検出 — 認証情報ログインと同じアクセス制御インフラストラクチャに属する管理対象デバイスの ID をサーバーがどのように検証するかについて説明します。
コメント
GitHub Issues · utterances