とき、webauthn-rails-demo-app もまたこちらを覗いているのだ
参考
シーケンス
登録
NRI より引用⓿ ブラウザから認証サーバーに登録リクエストを送信する
❶ 認証サーバーでチャレンジと呼ばれる文字列が生成され、サーバー情報とともにブラウザに送信される
❷ ❶の情報にユーザー情報を付加して、認証器に本人確認をリクエストする
❸ 認証器での本人確認が完了したら、公開鍵と秘密鍵のペアを作成する
❹ ❸で生成した秘密鍵で署名した署名情報、ユーザー情報、公開鍵をブラウザに送信する
❺ 認証サーバーに署名情報と公開鍵を送信し、DBに保存する
/registration/new へ username を持たせて POST(⓿ 登録リクエストの送信)すると以下のレスポンスが返ってくる(❶)
登録リクエスト
{ "challenge": "SO1-FIWH7cFa-P4KcgX1hsocLsQBi5yHdTEXYkLCR-E", "timeout": 120000, "rp": { "name": "WebAuthn Rails Demo App" }, "user": { "name": "shmn7iii2", "id": "JU7CVCwOPaFl4ZSWzZMifa9E6y3sioWax3BrEO-E9FRivtuP097yDcS6rCQmXSbceUEbi9KxD4rto-A5l35igQ", "displayName": "shmn7iii2" }, "pubKeyCredParams": [ { "type": "public-key", "alg": -7 }, { "type": "public-key", "alg": -37 }, { "type": "public-key", "alg": -257 } ], "authenticatorSelection": { "userVerification": "required" } }
ブラウザはこれを受け取り認証機を起動(❷)、鍵ペアを生成し(❸)サーバーへコールバックする(❹)
この辺りは javaScript で直書きされている
create(event) { var [data, status, xhr] = event.detail; console.log(data); var credentialOptions = data; // Registration if (credentialOptions["user"]) { var credential_nickname = event.target.querySelector("input[name='registration[nickname]']").value; var callback_url = `/registration/callback?credential_nickname=${credential_nickname}` Credential.create(encodeURI(callback_url), credentialOptions); } }
コールバック内容は以下
{ "type": "public-key", "id": "WlWIXxHCp-YI1fjZw6IFg2x7Mmsg8W_3wad6XFOp-iY", "rawId": "WlWIXxHCp-YI1fjZw6IFg2x7Mmsg8W_3wad6XFOp-iY", "response": { "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiRnlQR1Y3c3Q2c0ZjNVRDV0ltLWVBbTF1SmMtR0VLblZrTm8zOXNUNk1fbyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZX0", "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAK3OAAI1vMYKZIsLJfHwVQMAIFpViF8RwqfmCNX42cOiBYNsezJrIPFv98GnelxTqfompQECAyYgASFYIF0Y5cfyP2HdwZjsU_U7She_MLmhEowx10U9OJbvVJHFIlggc5bB95HMzrZNAYsJQU3AZpqMUbdfFsl8puUrH0tgHDg" }, "clientExtensionResults": { }, "credential_nickname": "mbp14", "controller": "registrations", "action": "callback", "registration": { "type": "public-key", "id": "WlWIXxHCp-YI1fjZw6IFg2x7Mmsg8W_3wad6XFOp-iY", "rawId": "WlWIXxHCp-YI1fjZw6IFg2x7Mmsg8W_3wad6XFOp-iY", "response": { "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiRnlQR1Y3c3Q2c0ZjNVRDV0ltLWVBbTF1SmMtR0VLblZrTm8zOXNUNk1fbyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZX0", "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAK3OAAI1vMYKZIsLJfHwVQMAIFpViF8RwqfmCNX42cOiBYNsezJrIPFv98GnelxTqfompQECAyYgASFYIF0Y5cfyP2HdwZjsU_U7She_MLmhEowx10U9OJbvVJHFIlggc5bB95HMzrZNAYsJQU3AZpqMUbdfFsl8puUrH0tgHDg" }, "clientExtensionResults": { } } }
サーバー側で検証しCredentialを生成し保存(❺)
以上で登録は完了
認証
NRI より引用⓿ブラウザから認証サーバーに認証リクエストを送信する
❶認証サーバーでチャレンジと呼ばれる文字列が生成され、サーバー情報とともにブラウザに送信される
❷認証器に本人確認をリクエストする
❸サーバー情報を元にユーザーの一覧が表示され、ユーザーを選択して本人確認を行う
❹認証器での本人確認が完了したら、ユーザーの秘密鍵で署名した署名情報、ユーザー情報をブラウザに送信する
❺DBに登録されているユーザーの公開鍵で署名検証し、ユーザーを認証する
/sessions/new に認証対象の username を持たせて POST(⓿)
以下レスポンス(❶)
{ "challenge": "sO-jSOvJj8Q8oD-eVWnACYtAvAC_ugSBDXV8ePb9EKI", "timeout": 120000, "allowCredentials": [ { "type": "public-key", "id": "3/h5Ff7wbcNEl1r3N4ONNauPpOqKANpc2vyYTqVN0k4=" } ], "userVerification": "required" }
ブラウザで認証器へ問い合わせ(❷❸❹)、認証結果をコールバック
{ "type": "public-key", "id": "3_h5Ff7wbcNEl1r3N4ONNauPpOqKANpc2vyYTqVN0k4", "rawId": "3_h5Ff7wbcNEl1r3N4ONNauPpOqKANpc2vyYTqVN0k4", "response": { "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoidTdVWVU1TVh1M25nOTBWc214NVVWQ0F0c21tNS1hUjBzV3VGLWtjY3dKVSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZSwib3RoZXJfa2V5c19jYW5fYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCJ9", "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAA", "signature": "MEUCIE2RDMfdVfqXTxPPjqVXfqzXe7WuqFgFqw8uFT07go6FAiEAhWf2mWUmPcrKoWhHrUof_O8FyfLvri5xAxqDuwpiXSI", "userHandle": "0gmhzaSFmDOeElgJuWLUPiSnPZKI2IRppaV0B36KXqNx8eWBvEF8SWwZ9UA39tS4oX3dhBGNLnVOYPDf4qVeJg" }, "clientExtensionResults": { }, "controller": "sessions", "action": "callback", "session": { "type": "public-key", "id": "3_h5Ff7wbcNEl1r3N4ONNauPpOqKANpc2vyYTqVN0k4", "rawId": "3_h5Ff7wbcNEl1r3N4ONNauPpOqKANpc2vyYTqVN0k4", "response": { "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoidTdVWVU1TVh1M25nOTBWc214NVVWQ0F0c21tNS1hUjBzV3VGLWtjY3dKVSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZSwib3RoZXJfa2V5c19jYW5fYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCJ9", "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAA", "signature": "MEUCIE2RDMfdVfqXTxPPjqVXfqzXe7WuqFgFqw8uFT07go6FAiEAhWf2mWUmPcrKoWhHrUof_O8FyfLvri5xAxqDuwpiXSI", "userHandle": "0gmhzaSFmDOeElgJuWLUPiSnPZKI2IRppaV0B36KXqNx8eWBvEF8SWwZ9UA39tS4oX3dhBGNLnVOYPDf4qVeJg" }, "clientExtensionResults": { } } }
サーバーでDBと照合・検証(❺)し成功でログイン