webauthn-rails-demo-app を覗く

とき、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と照合・検証(❺)し成功でログイン