WebAuthn(Web Authentication API)は、公開鍵暗号を使用してセキュアなパスワードレス認証を可能にするW3C標準です。パスワードの代わりに、ユーザーはハードウェアベースの認証器(指紋リーダー、Face ID、YubiKeyなど)またはプラットフォーム認証器(Touch IDなど組み込み型)を使用して登録・認証します。
Javascriptクライアントを使用した登録と認証の処理方法を以下に示します。
WebAuthn 登録の仕組み
ユーザーが登録を開始する:
ユーザーはユーザー名を入力し、登録をクリックします。
ブラウザがサーバーからオプションをリクエストする:
フロントエンドが登録オプションを取得するために POST リクエストを行います。
ブラウザーが認証情報を作成:
SimpleWebAuthnBrowser.startRegistration() を通じた navigator.credentials.create() API を使用して、クレデンシャルが作成されます。
サーバーが登録を検証します:
ブラウザは資格情報をサーバーに送り返します。
サーバーは登録を検証し、そのユーザーの公開鍵を保存します。
TsgcWSAPIServer_WebAuthn コンポーネントには WebAuthn プロトコルをテストするための HTML ファイルが含まれています。この HTML ファイルには、WebAuthn と対話するための最小限の UI と JavaScript が含まれています。
sgcWebAuthn.html のウォークスルー
Structure Overview:
Username Input: ユーザーの識別子をキャプチャします。
ボタン:
Register – WebAuthn登録を開始します。
Authenticate – ログインを開始します(同様に処理されます)。
Debug Console: リアルタイムのデバッグ情報(WebAuthnからのJSON)を表示します。
1. 入力用 HTML UI
<input type="text" id="username" name="username" autocomplete="username webauthn" />
<button id="btnRegBegin"><strong>Register</strong></button>
2. JavaScript: ボタンクリックハンドラ
document.querySelector('#btnRegBegin').addEventListener('click', async () => {
const username = document.getElementById("username").value;
if (username == "") {
document.getElementById('Error').innerText = 'Please enter a username to register';
return;
}
const resp = await fetch('<#webauthn_registration_options>', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, algorithms: [] })
});
const options = await resp.json();
const attResp = await startRegistration(options); // WebAuthn API
3. サーバー応答(HTML内の疑似エンドポイント)
fetch('/sgcWebAuthn/Registration/Options', ...)
fetch('/sgcWebAuthn/Registration/Verify', ...)
4. 登録の完了
const verificationResp = await fetch('/webauthn/register/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(attResp)
});
const verificationJSON = await verificationResp.json();
if (verificationJSON && verificationJSON.verified) {
document.getElementById('Success').innerHTML = `Authenticator registered!`;
}
WebAuthn認証の仕組み
ユーザーが認証を開始する:
ユーザーはユーザー名を入力し、登録をクリックします。
ブラウザがサーバーからオプションをリクエストする:
フロントエンドは認証オプションを取得するために POST リクエストを送信します。
ブラウザーが認証情報を作成:
navigator.credentials.get() API を SimpleWebAuthnBrowser.startAuthentication() 経由で使用します。
サーバーが認証を確認します:
ブラウザは資格情報をサーバーに送り返します。
サーバーは署名された結果を検証します。
1. HTML UI のセットアップ
<div class="container">
<h1>WebAuthn Authentication Sample</h1>
<section id="userdata">
<label for="username">Username:</label>
<input type="text" id="username" name="username" autocomplete="username webauthn" autofocus />
</section>
<button id="btnAuthBegin"><strong>Authenticate</strong></button>
<p id="Success" class="success"></p>
<p id="Error" class="error"></p>
<details open>
<summary>Console</summary>
<textarea id="Debug"></textarea>
</details>
</div>
2. 認証オプションを取得
startAuthentication を呼び出す前に、ユーザー名をサーバーに送信します。
const resp = await fetch('<#webauthn_authentication_options>', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: document.getElementById("username").value,
user_verification: 'preferred'
}),
});
サーバーは以下を含む JSON オブジェクトで応答します:
{
"challenge": "base64url-encoded-random-string",
"allowCredentials": [
{
"id": "base64url-credential-id",
"type": "public-key"
}
],
"userVerification": "preferred",
"rpId": "yourdomain.com"
}
これは PublicKeyCredentialRequestOptions と呼ばれます。
3. 認証器のレスポンスを受信
asseResp は次のようになります(簡略版):
{
"id": "credentialId",
"rawId": "base64url-encoded-id",
"response": {
"authenticatorData": "...",
"clientDataJSON": "...",
"signature": "...",
"userHandle": "..."
},
"type": "public-key",
"clientExtensionResults": {}
}
この応答は、ユーザーが次のことを証明します:
4. 署名された認証応答をサーバーに送信する
ユーザーが認証器と対話した後 (startAuthentication() を介して)、JavaScript でレスポンスオブジェクトを受け取ります。
const verificationResp = await fetch('/webauthn/authenticate/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(asseResp), // this is the signed response
});
5. サーバーのレスポンスを取得する
サーバーは次のような結果を返します。
{ "verified": true }
または何か問題が発生した場合:
{ "verified": false, "error": "Invalid signature" }
そしてフロントエンドコードで処理します:
const result = await verificationResp.json();
if (result.verified) {
document.getElementById('Success').textContent = 'User authenticated!';
} else {
document.getElementById('Error').textContent = 'Authentication failed!';
}