安全なお支払いの確認による認証

Eiji Kitamura
Eiji Kitamura

販売者は、特定のクレジット カードまたは銀行口座に対する強力な顧客認証(SCA)プロセスの一環として、安全なお支払いの確認(SPC)を使用できます。WebAuthn は認証を行います(多くの場合、生体認証を通じて)。WebAuthn は事前に登録する必要があります。詳しくは、安全なお支払いの確認の登録をご覧ください。

一般的な実装の仕組み

SPC の最も一般的な用途は、顧客が販売者のサイトで購入し、クレジット カード発行会社または銀行が支払者の認証を必要とする場合です。

認証ワークフロー。

認証フローを見てみましょう。

  1. 顧客が支払い認証情報(クレジット カード情報など)を販売者に提供します。
  2. 販売者は、支払い認証情報に対応するカード発行会社または銀行(証明書利用者または RP)に、支払者に個別の認証が必要な場合に問い合わせます。この交換は、EMV® 3-D Secure などで行われる可能性があります。
    • RP が販売者に SPC を使用することを希望している場合、ユーザーが以前に登録している場合、RP は支払人によって登録された認証情報 ID のリストとチャレンジを返します。
    • 認証が不要な場合、販売者は引き続き取引を完了できます。
  3. 認証が必要な場合は、ブラウザが SPC をサポートしているかどうかを確認します。
    • ブラウザが SPC をサポートしていない場合は、既存の認証フローに進みます。
  4. 販売者が SPC を呼び出します。ブラウザに確認ダイアログが表示されます。
    • RP から認証情報 ID が渡されていない場合は、既存の認証フローにフォールバックします。認証が成功したら、今後の認証を効率化するために SPC 登録の使用を検討してください
  5. ユーザーはデバイスのロックを解除して、支払いの金額と支払い先を確認して認証します。
  6. 販売者は認証によって認証情報を受け取ります。
  7. RP は販売者から認証情報を受信し、その真正性を検証します。
  8. RP がオーナー確認の結果を販売者に送信します。
  9. 販売者は、支払いの成否を示すメッセージをユーザーに表示します。

特徴検出

ブラウザで SPC がサポートされているかどうかを検出するには、canMakePayment() に疑似呼び出しを送信します。

販売者のウェブサイトで SPC を機能検出するには、次のコードをコピーして貼り付けます。

const isSecurePaymentConfirmationSupported = async () => {
  if (!'PaymentRequest' in window) {
    return [false, 'Payment Request API is not supported'];
  }

  try {
    // The data below is the minimum required to create the request and
    // check if a payment can be made.
    const supportedInstruments = [
      {
        supportedMethods: "secure-payment-confirmation",
        data: {
          // RP's hostname as its ID
          rpId: 'rp.example',
          // A dummy credential ID
          credentialIds: [new Uint8Array(1)],
          // A dummy challenge
          challenge: new Uint8Array(1),
          instrument: {
            // Non-empty display name string
            displayName: ' ',
            // Transparent-black pixel.
            icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg==',
          },
          // A dummy merchant origin
          payeeOrigin: 'https://non-existent.example',
        }
      }
    ];

    const details = {
      // Dummy shopping details
      total: {label: 'Total', amount: {currency: 'USD', value: '0'}},
    };

    const request = new PaymentRequest(supportedInstruments, details);
    const canMakePayment = await request.canMakePayment();
    return [canMakePayment, canMakePayment ? '' : 'SPC is not available'];
  } catch (error) {
    console.error(error);
    return [false, error.message];
  }
};

isSecurePaymentConfirmationSupported().then(result => {
  const [isSecurePaymentConfirmationSupported, reason] = result;
  if (isSecurePaymentConfirmationSupported) {
    // Display the payment button that invokes SPC.
  } else {
    // Fallback to the legacy authentication method.
  }
});

お客様を認証する

ユーザーを認証するには、secure-payment-confirmation パラメータと WebAuthn パラメータを指定して PaymentRequest.show() メソッドを呼び出します。

お支払い方法の data プロパティ(SecurePaymentConfirmationRequest)に渡す必要があるパラメータは次のとおりです。

パラメータ 説明
rpId RP 送信元のホスト名(RP ID 形式)。
challenge リプレイ攻撃を防ぐランダム チャレンジ。
credentialIds 認証情報 ID の配列。WebAuthn の認証では、allowCredentials プロパティは PublicKeyCredentialDescriptor オブジェクトの配列を受け入れますが、SPC で渡すのは認証情報 ID のリストのみです。
payeeName(任意) お支払い受取人の名前。
payeeOrigin お支払い受取人の出身国。上記のシナリオでは、それは販売者のオリジンです。
instrument 画像リソースを指す displayName の文字列と icon の URL。リクエストが成功するには、アイコンが正常に取得されて表示される必要があることを示す iconMustBeShown のブール値(デフォルトは true)です。省略可能です。
timeout トランザクションに署名するためのタイムアウト(ミリ秒)
extensions WebAuthn 呼び出しに拡張機能が追加されました。「payment」拡張子を自身で指定する必要はありません。

次のサンプルコードを確認してください。

// After confirming SPC is available on this browser via a feature detection,
// fetch the request options cross-origin from the RP server.
const options = fetchFromServer('https://rp.example/spc-auth-request');
const { credentialIds, challenge } = options;

const request = new PaymentRequest([{
  // Specify `secure-payment-confirmation` as payment method.
  supportedMethods: "secure-payment-confirmation",
  data: {
    // The RP ID
    rpId: 'rp.example',

    // List of credential IDs obtained from the RP server.
    credentialIds,

    // The challenge is also obtained from the RP server.
    challenge,

    // A display name and an icon that represent the payment instrument.
    instrument: {
      displayName: "Fancy Card ****1234",
      icon: "https://rp.example/card-art.png",
      iconMustBeShown: false
    },

    // The origin of the payee (merchant)
    payeeOrigin: "https://merchant.example",

    // The number of milliseconds to timeout.
    timeout: 360000,  // 6 minutes
  }
}], {
  // Payment details.
  total: {
    label: "Total",
    amount: {
      currency: "USD",
      value: "5.00",
    },
  },
});

try {
  const response = await request.show();

  // response.details is a PublicKeyCredential, with a clientDataJSON that
  // contains the transaction data for verification by the issuing bank.
  // Make sure to serialize the binary part of the credential before
  // transferring to the server.
  const result = fetchFromServer('https://rp.example/spc-auth-response', response.details);
  if (result.success) {
    await response.complete('success');
  } else {
    await response.complete('fail');
  }
} catch (err) {
  // SPC cannot be used; merchant should fallback to traditional flows
  console.error(err);
}

.show() 関数は PaymentResponse オブジェクトを生成しますが、details には、RP による検証用のトランザクション データ(payment)を含む clientDataJSON を持つ公開鍵認証情報が含まれます。

生成された認証情報をクロスオリジンで RP に転送し、検証する必要があります。

RP がトランザクションを検証する方法

RP サーバーでトランザクション データを確認することは、支払いプロセスにおける最も重要なステップです。

RP は、WebAuthn の認証アサーション検証プロセスに沿ってトランザクション データを検証できます。また、payment を検証する必要もあります。

clientDataJSON のペイロードの例:

{
  "type":"payment.get",
  "challenge":"SAxYy64IvwWpoqpr8JV1CVLHDNLKXlxbtPv4Xg3cnoc",
  "origin":"https://spc-merchant.glitch.me",
  "crossOrigin":false,
  "payment":{
    "rp":"spc-rp.glitch.me",
    "topOrigin":"https://spc-merchant.glitch.me",
    "payeeOrigin":"https://spc-merchant.glitch.me",
    "total":{
      "value":"15.00",
      "currency":"USD"
    },
    "instrument":{
      "icon":"https://cdn.glitch.me/94838ffe-241b-4a67-a9e0-290bfe34c351%2Fbank.png?v=1639111444422",
      "displayName":"Fancy Card 825809751248"
    }
  }
}
  • rp は RP のオリジンと一致します。
  • topOrigin は、RP が想定するトップレベルのオリジン(上記の例では販売者のオリジン)と一致します。
  • payeeOrigin は、ユーザーに表示されるはずのお支払い受取人の発行元と一致します。
  • total は、ユーザーに表示されるはずの取引金額と一致します。
  • instrument は、ユーザーに表示されるはずの支払い方法の詳細と一致します。
const clientData = base64url.decode(response.clientDataJSON);
const clientDataJSON = JSON.parse(clientData);

if (!clientDataJSON.payment) {
  throw 'The credential does not contain payment payload.';
}

const payment = clientDataJSON.payment;
if (payment.rp !== expectedRPID ||
    payment.topOrigin !== expectedOrigin ||
    payment.payeeOrigin !== expectedOrigin ||
    payment.total.value !== '15.00' ||
    payment.total.currency !== 'USD') {
  throw 'Malformed payment information.';
}

すべての検証基準に合格すると、RP は販売者に取引が成功したことを通知できます。

次のステップ