実装方法

FIDO認証の開発を簡単にするPKaaS

主に生体認証を利用するFIDO認証は、セキュリティやユーザビリティを向上させる認証技術の1つとして注目されています。公開鍵ベースの認証方法をサービスとして提供するPublic Key based authentication as a Service (PKaaS)は開発者の方にFIDO認証をより簡単に実装してもらうための構想です。

このページは、開発者がPKaaSを利用する際に必要な情報を提供します。

【注】PKaaSの機能は現在限定した開発者の方にのみ提供しています。

FIDO認証とPKaaS

FIDO認証は、オンラインでセキュリティや利便性の向上を目的とした認証技術です。

FIDO認証の処理フロー図

この図はFIDO認証の処理を示しています。FIDO認証を提供する場合にはFIDOサーバーを構築する必要があり、それはチャレンジの発行や公開鍵の保存、電子署名の検証など様々な処理を行う必要があります。

これまで、FIDO認証を提供しようとすると、認証サービス提供者(IdP)がそれぞれ独自にFIDOサーバーを構築・運用する必要があり、技術的・運用的な負担がかかることが課題でした。PKaaSはFIDO認証に必要な機能をクラウドサービスとして提供することで、IdPが容易にFIDO認証を提供するお手伝いをします。

PKaaSを利用した際のFIDO認証用公開鍵の保存場所を示す図

この図は、PKaaSを利用した際のFIDO認証用公開鍵の保存場所を示しています。従来のようにIdPが独自にFIDOサーバーを構築した場合、署名検証や公開鍵の保存をそれぞれで行う必要があります。PKaaSを利用すると、これらの作業をPKaaSに任せることができますので、IdPは従来に比べて簡単にFIDO認証を利用者へ提供することが可能になります。

PKaaSの利用形態

PKaaSにはAPI版とリダイレクト版があります。ここでは、双方の利用形態の違いについて説明します。

API版

API版は、WebAPIを通してFIDO認証を実施する形態です。

PKaaSの認証画面が表示されないので、IdPの認証時の画面のみを表示したい方に便利な利用方法です。API版を利用する場合には、下記の開発が必要です。

  • IdPの認証画面へJavaScriptを配置
  • JavaScriptが呼び出すWebAuthnのエンドポイントおよびPKaaS APIの呼び出し

リダイレクト版

リダイレクト版は利用者のIdPの認証画面がPKaaSの認証画面にリダイレクトし、FIDO認証を実施する利用方法です。

FIDO認証用の画面を用意しないIdPにおすすめの使い方です。

現在リダイレクト版に関してはどなたにもご利用いただけません。

やさい大学のデモで挙動をご確認ください。

PKaaSの主な機能

この図は、API版を利用した際の認証システム全体の構成例です。開発者は自分の認証ページを用意するドメイン上にFIDO認証用のエンドポイント(JavaScriptを経由して利用者のブラウザからのリクエストを一旦受け取る機能を持つサーバー機能)を設置する必要があります。

API版を利用した際の認証システム全体の構成例

FIDO認証には利用者の公開鍵を紐づける登録と、登録されている公開鍵を利用する認証の2つの機能があり、その両方で①チャレンジの発行と②電子署名の検証という2つの機能をこの順番で実行する必要があります。

  • ①チャレンジの発行: チャレンジと呼ばれる乱数を発行します。
  • ②署名検証: 登録・認証時には①で発行したチャレンジを含んだ文書を適切な公開鍵を使って電子署名を利用者のPCやスマホが作成します。この機能はそれぞれの形式に沿った署名を検証し、その結果を返します。

API版利用の流れ

1. Yahoo! JAPAN IDを取得する

最初に「Yahoo! JAPAN ID」を取得してください。

Yahoo! JAPAN IDを登録するには

2. アプリケーションを登録する

手順1で取得した「Yahoo! JAPAN ID」でログインして、下記ページからアプリケーションを登録してください。

アプリケーションの登録

3. Client IDを利用して認証用エンドポイントを開発する

手順2で登録したClient ID(アプリケーションID)は認証用エンドポイントを設置するサーバーからAPIをリクエストする際に利用します。

4. PKaaS APIの事前利用連絡を行う(現在募集していません)

PKaaSを利用するためには、個別のドメインや通信について事前にご連絡いただいたうえで、許諾可能かどうかを判断いたします。現在利用連絡は限定したお客様以外から受け付けておりません。

APIリファレンス

PKaaSが提供するAPIは以下の通りです。

PKaaSはFIDO登録とFIDO認証に関する2対のAPIを提供しています。必ず「登録のチャレンジ発行」→「登録の署名検証」もしくは「認証のチャレンジ発行」→「認証の署名検証」の順番で実行してください。

エンドポイント 概要
/api/attestation/options 登録のチャレンジ発行
/api/attestation/result 登録の署名検証
/api/assertion/options 認証のチャレンジ発行
/api/assertion/result 認証の署名検証

共通ヘッダー

これらすべてのAPIリクエストには下記のヘッダーを付与してください。

パラメータ 必須 説明
User-agent string "Yahoo-AppID:"に続けて、前の手続きで取得したClientIDを入力してください。
origin string 前の手順にて申請済みの開発者サイトのドメインを入力してください。

登録のチャレンジ生成

リクエストURL
https://pkaas.yahooapis.jp/api/attestation/options
メソッド
POST
Content-Type
application/json

リクエストパラメータ

"appid"以外については、標準的なブラウザの実装により対応状況が異なります。MDNなどを参考にしてください。

パラメータ 必須 説明
username string PKaaSがユーザーを識別するための識別子です。開発者サイトで利用しているユーザー名と"#"に続けて、申請済みの開発者サイトのドメインを結合してください。例えばユーザー名が「sample1234」、開発者サイトのドメインが「example.com」の場合にはsample1234#example.comとしてください。
displayName string ユーザーの表示に用いるためのニックネームです。
authenticatorSelection array(string) MDNを参照してください。
attestation string MDNを参照してください。

サンプルリクエスト

curl 'https://pkaas.yahooapis.jp/api/attestation/options' \
  -H "Content-Type: application/json" \
  -H "User-agent: Yahoo-AppID:<Client ID>" \
  -H "Origin: <YOUR DOMAIN>" \
  --data-raw '{"username":"sample1234#example.com","displayName":"sample1234#example.com","authenticatorSelection":{"requireResidentKey":true,"userVerification":"preferred","authenticatorAttachment":"platform"},"attestation":"none"}'

レスポンスパラメータ

エラー時のレスポンスはYahoo!デベロッパーネットワークのエラーメッセージもしくは、LINE FIDO2 serverのエラーメッセージに準じます。

パラメータ 説明
status string 処理結果を示します。"ok": 処理に成功、"failed":処理に失敗
errorMessage array(string) LINE FIDO2 serverの内部エラーに準じたエラーメッセージです。
sessionId string セッションIDです。
(rp以下) Navigator.credentials.create()のパラメータに準じます。登録するサイトのドメインやチャレンジなどが含まれています。詳しくはMDNなどを参照してください。

サンプルレスポンス

{
    "status": "ok",
    "errorMessage": "",
    "sessionId": "",
    "rp": {
        "name": "PKaaS Demo",
        "icon": "",
        "id": "example.com"
    },
    "user": {
        "name": "sample1234#example.com",
        "icon": "",
        "id": "n99PZyE5b_muRkeyRUyvCgg4XsXg5YA816PVVO0UqBI",
        "displayName": "sample1234#example.com"
    },
    "challenge": "PTlwETmKOkcS1m3__3__1LDf1kWX8OZIZCZYSqdWzoJRWUAkvRb9velN98Wvdgd0VwNCwBpX_-UKEA3Fv4dRRA",
    "pubKeyCredParams": [
        {
            "type": "public-key",
            "alg": -65535
        },
        {
            "type": "public-key",
            "alg": -257
        }
        // ... (その他のパラメータ)
    ],
    "timeout": 180000,
    "excludeCredentials": [],
    "authenticatorSelection": {
        "authenticatorAttachment": "platform",
        "requireResidentKey": true,
        "userVerification": "preferred"
    },
    "attestation": "none",
    "extensions": {
        "credProps": true
    }
}

登録の署名検証

リクエストURL
https://pkaas.yahooapis.jp/api/attestation/result
メソッド
POST
Content-Type
application/json

リクエストパラメータ

"appid"以外については、標準的なブラウザの実装により対応状況が異なります。MDNなどを参考にしてください。

パラメータ 必須 説明
rawId MDNを参照してください。
id MDNを参照してください。
response MDNを参照してください。
type string "public-key"を指定してください。
extensions MDNを参照してください。

サンプルリクエスト

curl 'https://pkaas.yahooapis.jp/api/attestation/result' \
  -H "Content-Type: application/json" \
  -H "User-agent: Yahoo-AppID:<Client ID>" \
  -H "Origin: <YOUR DOMAIN>" \
  --data-raw '{"rawId":"sampleSample","id":"sampleSample","response":{"clientDataJSON":"eyJ....","transports":["hybrid","internal"]},"type":"public-key","extensions":{"credProps":{"rk":true}}}'

レスポンスパラメータ

パラメータ 説明
status string 処理結果を示します。"ok": 処理に成功、"failed":処理に失敗
errorMessage array(string) LINE FIDO2 serverの内部エラーに準じたエラーメッセージです。詳しくはこちらを参照してください。
sessionId string セッションIDです。

サンプルレスポンス

{"status":"ok","errorMessage":"","sessionId":""}

認証のチャレンジ発行

リクエストURL
https://pkaas.yahooapis.jp/api/assertion/options
メソッド
POST
Content-Type
application/json

リクエストパラメータ

標準的なブラウザの実装により対応状況が異なります。MDNなどを参考にしてください。

パラメータ 必須 説明
username string PKaaSがユーザーを識別するための識別子です。開発者サイトで利用しているユーザー名と"#"に続けて、申請済みの開発者サイトのドメインを結合してください。例えばユーザー名が「sample1234」、開発者サイトのドメインが「example.com」の場合にはsample1234#example.comとしてください。
userVerification string 認証器の検証方法を指定します。
"required" 必ずユーザー検証を伴う認証器を使用します。
"preferred" ユーザー検証が可能であれば行いますが、必須ではありません。
"discouraged" ユーザー検証を行わない認証器を優先します。

サンプルリクエスト

curl 'http://localhost:8888/assertion/options' \
  -H "Content-Type: application/json" \
  -H "User-agent: Yahoo-AppID:<Client ID>" \
  -H "Origin: <YOUR DOMAIN>" \
  --data-raw '{"username":"sample1234#example.com","userVerification":"preferred"}'

レスポンスパラメータ

パラメータ 説明
status string 処理結果を示します。"ok": 処理に成功、"failed":処理に失敗
errorMessage array(string) LINE FIDO2 serverの内部エラーに準じたエラーメッセージです。
sessionId string セッションIDです。
(challenge以下) Navigator.credentials.get()のパラメータに準じます。登録するサイトのドメインやチャレンジなどが含まれています。詳しくはMDNなどを参照してください。

サンプルレスポンス

{
    "status": "ok",
    "errorMessage": "",
    "sessionId": "",
    "challenge": "ssSafaXDbmLDxtQnFPALcUYWDdOT180yOoay-P0EdeDVj6uKvo28chUZOU2fLc_7LJK1wlj-ebg02iRQQPgVtw",
    "timeout": 180000,
    "rpId": "example.com",
    "allowCredentials": [
        {
            "type": "public-key",
            "id": "ugHWbvJQEDUeqLzA5wZqO6KEnT-lRLtMHbLbboNIDDc",
            "transports": [
                "internal"
            ]
        }
    ],
    "userVerification": "preferred",
    "extensions": {}
}

認証の署名検証

リクエストURL
https://pkaas.yahooapis.jp/api/assertion/result
メソッド
POST
Content-Type
application/json

リクエストパラメータ

標準的なブラウザの実装により対応状況が異なります。MDNなどを参考にしてください。

パラメータ 必須 説明
rawId string クレデンシャル識別子(credentialId)
id string クレデンシャル識別子(credentialId)のBase64URLエンコードした文字列
response list Navigator.credentials.get()のパラメータに準じます。登録するサイトのドメインやチャレンジなどが含まれています。詳しくはMDNなどを参照してください。

サンプルリクエスト

curl 'https://pkaas.yahooapis.jp/api/assertion/result' \
  -H "Content-Type: application/json" \
  -H "User-agent: Yahoo-AppID:<Client ID>" \
  -H "Origin: <YOUR DOMAIN>" \
  --data-raw '{"rawId":"sampleSample","id":"sampleSample","response":{"clientDataJSON":"eyJ...","signature":"...","authenticatorData":"..."},"type":"public-key","extensions":{}}'

レスポンスパラメータ

パラメータ 説明
status string 処理結果を示します。"ok": 処理に成功、"failed":処理に失敗
errorMessage array(string) LINE FIDO2 serverの内部エラーに準じたエラーメッセージです。
sessionId string セッションIDです。

サンプルレスポンス

{"status":"ok","errorMessage":"","sessionId":""}

API版サンプルコード

API版PKaaSを利用する際のサンプルコードを示します。

API版PKaaSの実装例はこちらを参照してください。

PKaaSデモの対応機種・ブラウザ

  • • Android: バージョン9.0以上、画面ロックを設定済み
  • • iOS/iPadOS: バージョン16以上、画面ロックを設定済み
  • • macOS: バージョン13(macOS Ventura)以上、画面ロックを設定済み
  • • 推奨ブラウザ:
  • - Google Chrome
  • - Microsoft Edge
  • - Safari
  • *一部のバージョンは非対応の場合があります。

開発者のサーバーに設置するもの

認証用ページ(HTML)

まず、認証用ページとして、以下のようなHTMLを設置します。

FIDO認証を実現するために、「ユーザー名」を入れるだけでFIDO登録・認証を行うことを目的としています。

実際に導入をご検討いただく際には、ご自身のサイトに合わせた適切なデザインや設計が必要です。

この設計は、利用者による不正を防ぐために重要な役割を果たします。例えば、既存の認証方法にPKaaSを利用したFIDO認証を追加する場合、既存の認証方法で成功したユーザーにのみFIDO登録を行わせるように設計することで、他人のアカウントに対してFIDO登録することを防ぐことが可能です。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>PKaaS Demo</title>
</head>
<body>
  <h1>PKaaSデモ検証用サイト</h1>
 
  <div>
    <label for="username">ユーザー名:</label>
    <input type="text" id="username" name="username">
  </div>
 
  <div>
    <button id="register-button">登録</button>
    <button id="authenticate-button">認証</button>
  </div>
 
  <div>
    <pre id="msg"></pre>
  </div>
 
  <!-- 次の手順で設置するjsを呼び出すパスを設定してください。 -->
  <script src="js/index.js"></script>
</body>
</html>

認証用ページから読み込むJavaScript

FIDO認証をブラウザ上で実現する仕組みは、Web Authentication(WebAuthn)と呼ばれます。WebAuthnでは、JavaScriptを用いて、ブラウザから生体認証などの認証機能を呼び出したり、生成した電子署名をサーバーへ送信したりします。今回は、オープンソースソフトウェア(OSS)として公開されている以下のライブラリに含まれるJavaScriptを利用します。

LINE FIDO2 Server

JavaScript内から呼び出すためのエンドポイント

前の手順で設置したJavaScriptからサーバーと通信する際、ブラウザは自身のドメイン内に設置されたエンドポイントへの通信のみを許可しています。これは、ページに埋め込まれた悪意あるJavaScriptが、ユーザーの意図しない形で攻撃者の用意した別ドメインへ通信するのを防ぐため、ブラウザによって課されているセキュリティ制限です。

そのため、JavaScriptはまず自身のWebサイト内のサーバーへリクエストを送信する必要があります。受け取ったリクエストは、ご自身のサーバーからPKaaSへ転送することで、チャレンジの発行や署名検証の結果を取得できます。この際、ブラウザからご自身のサーバーが受け取ったリクエストに、適切なAppIDを付加する必要があります。

以下に、Node.jsを用いて開発者サイトにFIDO認証用のエンドポイントを実装する際のサンプルコードを示します。

// Node.jsサンプルコード
const http = require('http');
const fs = require('fs');
const path = require('path');
const httpProxy = require('http-proxy');

// プロキシ先のURLを設定 
const TARGET_URL = 'https://pkaas.yahooapis.jp';

// http-proxyインスタンス作成
const proxy = httpProxy.createProxyServer({});

// サーバを作成
const server = http.createServer((req, res) => {
  if (req.url === '/') {
    // ルートリクエストが来たらindex.htmlを返す
    const filePath = path.join(__dirname, 'index.html');
    fs.readFile(filePath, (err, content) => {
      if (err) {
        res.writeHead(500, { 'Content-Type': 'text/plain' });
        res.end('サーバエラーが発生しました。');
        return;
      }

      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end(content, 'utf-8');
    });
  } else if (req.url.startsWith('/assertion/') || req.url.startsWith('/attestation/')) {
    // /assertion/* または /attestation/* の時はプロキシ
    proxy.web(req, res, { target: TARGET_URL }, (err) => {
      console.error('プロキシエラー:', err);
      res.writeHead(500, { 'Content-Type': 'text/plain' });
      res.end('プロキシエラーが発生しました。');
    });
  } else {
    // その他のリクエストは404にする
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('ページが見つかりません。');
  }
});

// 3000番ポートで待ち受け
server.listen(3000, () => {
  console.log(`Server listening on http://localhost:3000`);
});