はじめに

近年のWebログインにおけるセキュリティの潮流として、パスキー(Passkey)を使ったパスワードレスログインを導入するサイトが増えています。

New Relicの最新の Scripted Browser ランタイムをご利用いただくことで、パスキー認証を必要とするウェブサイトのログイン監視にも対応できるようになりました。今回はその具体的な実装方法をご紹介します。

パスキー監視を実現する仕組み:Virtual Authenticator

パスキー認証は「WebAuthn(Web Authentication)」という標準規格に基づいて動作し、ブラウザを経由してOSやデバイスのローカル認証システム(指紋認証や顔認証など)を呼び出します。

しかし、自動監視スクリプトが物理的なデバイスの生体認証を直接操作することはできません。そこで今回は、Selenium 4.xで提供されているVirtual Authenticator(仮想認証器)という、認証システムをエミュレートする仕組みを利用します。

実装の全体フロー

パスキーのクレデンシャル(認証情報)はセキュリティ上の理由からOSやデバイスに保存されており、通常の操作では保存された内容を取得できない仕様になっています。

そのため、監視環境を構築する際は以下の2つのステップに分けて進めます。

  1. クレデンシャル情報の取得
  2. 実際のサイトへのログイン監視スクリプトを作成

※ログイン監視の運用を行うことが目的であれば、ステップ1(クレデンシャルの取得)は基本的に初回1回のみ行えば問題ありません。

ステップ 1:クレデンシャル情報の取得方法(初回のみ)

まずはSynthetics上でVirtual Authenticatorを利用し、デバイスに保存されるべきクレデンシャル情報を取得するための「使い捨てスクリプト」を実行します。

1. 仮想認証器の有効化

スクリプトの先頭に以下のコードを追加して、Virtual Authenticatorを有効にします。

const { VirtualAuthenticatorOptions, Protocol, Transport } =
  require('selenium-webdriver/lib/virtual_authenticator');

// ---- 仮想認証器のセットアップ ----
const options = new VirtualAuthenticatorOptions();
options.setProtocol(Protocol.CTAP2);        // パスキー (FIDO2)
options.setTransport(Transport.INTERNAL);   // platform authenticator(パスキー/UVPA 用)
options.setHasResidentKey(true);            // Discoverable Credential
options.setHasUserVerification(true);       // UV 有効
options.setIsUserConsenting(true);          // 常に同意
options.setIsUserVerified(true);            // UV 済みとして扱う(UV 必須操作を成功させる)

await $webDriver.addVirtualAuthenticator(options);

2. パスキーの登録操作

続いて、テスト対象のWebサイトにアクセスし、パスキーを新規発行(登録)する一連の操作を行うスクリプトを記述してください。

3. ログ出力の実装

最後に以下のコードを追加し、Virtual Authenticatorに登録されたCredential IDなどの情報をログに出力させ、Virtual Authenticatorを削除します。

const credentialList = await $webDriver.getCredentials();
if (credentialList.length === 0) {
  throw new Error('Passkey registration did not complete');
}
console.log('credentials:', credentialList);
console.log('Successfully signed in and created a passkey!');

// バイナリデータ(credentialId / userHandle / privateKey)のみ base64 化し、それぞれ個別に保存する
const cred = credentialList[0];
const passkeyCredentialId = Buffer.from(cred._id).toString('base64');
const passkeyUserHandle = cred._userHandle ? Buffer.from(cred._userHandle).toString('base64') : '';
const passkeyPrivateKey = Buffer.from(cred._privateKey, 'binary').toString('base64');
const passkeySignCount = cred._signCount;
const passkeyRpId = cred._rpId;
const passkeyIsResidentCredential = cred._isResidentCredential;

console.log('PASSKEY_CREDENTIAL_ID=' + passkeyCredentialId);
console.log('PASSKEY_USER_HANDLE=' + passkeyUserHandle);
console.log('PASSKEY_PRIVATE_KEY=' + passkeyPrivateKey);
console.log('PASSKEY_SIGN_COUNT=' + passkeySignCount);
console.log('PASSKEY_RP_ID=' + passkeyRpId);
console.log('PASSKEY_IS_RESIDENT_CREDENTIAL=' + passkeyIsResidentCredential);

await $webDriver.removeVirtualAuthenticator();

4. ログの確認と情報の回収

スクリプトを実行し、正常に完了することを確認します。
テスト結果のScript logタブを開くと、ログの中に PASSKEY_CREDENTIAL_ID= などの値が出力されていますので、この値を手元に控えておきます。

ステップ 2:クレデンシャル情報を使ったサイトへのログイン監視

次に、ステップ1で取得したクレデンシャル情報を用いて、実際にログイン監視を常時行うためのスクリプトを作成します。

1. 仮想認証器の再初期化とクレデンシャルの登録

新しい監視用スクリプトの先頭に、以下のコードを追加します。ここには、ステップ1と同様のVirtual Authenticatorの初期化処理と、事前に取得したクレデンシャル情報を仮想認証器に登録するための処理が含まれています。

const { VirtualAuthenticatorOptions, Protocol, Transport, Credential } =
  require('selenium-webdriver/lib/virtual_authenticator');

// パスキーの各データを復元
// 一部に関してはSecure Credential(推奨)を利用すること推奨
const PASSKEY_CREDENTIAL_ID = 'BASE64_CREDENTIAL_ID'; //TODO 取得した値を設定
const PASSKEY_USER_HANDLE = 'BASE64_USER_HANDLE'; //TODO 取得した値を設定
const PASSKEY_PRIVATE_KEY = 'BASE64_PRIVATE_KEY'; //TODO 取得した値を設定
const PASSKEY_SIGN_COUNT = 1; //TODO 取得した値を設定
const PASSKEY_RP_ID = 'RP ID'; //TODO 取得した値を設定
const PASSKEY_IS_RESIDENT_CREDENTIAL = true; //TODO 取得した値を設定

// ---- 仮想認証器を用意(この時点では credential は登録しない) ----
const options = new VirtualAuthenticatorOptions();
options.setProtocol(Protocol.CTAP2);
options.setTransport(Transport.INTERNAL);
options.setHasResidentKey(true);
options.setHasUserVerification(true);
options.setIsUserConsenting(true);
options.setIsUserVerified(true);

await $webDriver.addVirtualAuthenticator(options);

// 個別の base64 値から Credential を復元する
function restoreCredential() {
  const id = new Uint8Array(Buffer.from(PASSKEY_CREDENTIAL_ID, 'base64'));
  const userHandle = PASSKEY_USER_HANDLE
    ? new Uint8Array(Buffer.from(PASSKEY_USER_HANDLE, 'base64'))
    : null;
  // privateKey は元がバイナリ文字列なので base64 → 'binary' 文字列に戻す
  const privateKey = Buffer.from(PASSKEY_PRIVATE_KEY, 'base64').toString('binary');
  return PASSKEY_IS_RESIDENT_CREDENTIAL
    ? Credential.createResidentCredential(id, PASSKEY_RP_ID, userHandle, privateKey, PASSKEY_SIGN_COUNT)
    : Credential.createNonResidentCredential(id, PASSKEY_RP_ID, privateKey, PASSKEY_SIGN_COUNT);
}

※コード内の指定箇所に、ステップ1で取得した「PASSKEY_CREDENTIAL_ID」などの実際の値を設定(移植)してください。

💡 セキュリティに関するベストプラクティス

本ブログの解説ではわかりやすさを重視してクレデンシャル情報をコード内に直接記述していますが、実際の運用(本番環境での監視など)では、New RelicのSecure Credentials機能に値を保存し、それを安全に呼び出して利用することを強く推奨します。

2. ログイン処理の実装

テスト対象サイトでパスキー認証を起動するボタンをクリックする操作の直前に、以下のコードを追加してクレデンシャル情報を適用します。

await $webDriver.addCredential(restoreCredential());

⚠️ 動作時の注意点

通常のユーザー操作では生体認証を求めるダイアログが表示されますが、Virtual Authenticatorを利用した場合はそのような表示は行われず、自動的にそのまま認証がクリアされます。

3. 仮想認証器の破棄

認証完了後、処理の最後には以下のコードを追加して必ずVirtual Authenticatorを削除してください。

await $webDriver.removeVirtualAuthenticator();

まとめ

ここまでの手順(仮想認証器の有効化 ➡ クレデンシャルの適用 ➡ 仮想認証器の削除)をターゲットサイト向けの監視シナリオに組み込むことで、パスキー認証を伴うWebログインの自動監視(Synthetics)を問題なく実装できます。最新ランタイムのパワーを活用し、モダンなセキュリティを導入したサイトのサービス品質担保にお役立てください。