次世代の認証技術 WebAuthnを紹介【前編】
みなさん、こんにちは。
NRIデジタルの工藤です。
私たちは、CoE活動として、新技術の探索・調査を行っています。
今回は次世代の認証技術として注目されている「WebAuthn」について前後編でご紹介します。
本記事ではWebAuthnの概要、WebAuthnを実現するための構成要素、実装シーケンスについてご紹介します。後半記事ではWebAuthnが抱える問題点とその解決策としてのPassKeyについてご紹介します。
WebAuthnとは
多くの人が、多数のWebサービスを日々利用しているかと思います。そんな中、複数のサービスのパスワードポリシーに応じてパスワードを設定しなければならないなど、パスワード管理に頭を悩ませている人も少なくないのではないでしょうか。パスワード認証はWebサービス利用を支える重要な技術ですが、課題があることも事実です。
このような悩みを解決するための技術の1つにFIDO(Fast Identity Online)があります。FIDOは生体情報やPINを用いて認証を行う技術で、ユーザはパスワードを入力する必要がなくなります。FIDO自体は、広く普及しておりiPhoneやAndroidのアプリから、パスワードを入力することなく、指紋認証や顔認証を利用してログインを行った経験がある方も多いかと思います。
FIDOは今まではネイティブアプリ用の規格しか用意されていませんでしたが、ブラウザ上で実行される、Webアプリ用にも標準規格が策定されました。それがWebAuthnです。
WebAuthnの仕組み
WebAuthnの仕組みを理解するには、まずはFIDOの仕組みについて知っておく必要があります。
FIDOは、ユーザのデバイスで生体認証やPINによる本人確認を行い、秘密鍵で暗号化した署名をサーバー側に送り、公開鍵で検証することでサーバー認証を行います。そのため、認証情報をネットワークで流す必要がありません。また、サーバーに保管されることもないためパスワード漏洩のリスクが低減します。
WebAuthnの実態は、Webブラウザ上のアプリケーションが利用するJavaScript APIであり、このAPIを利用することで、Webアプリケーション上でFIDO認証を行うことができます。
WebAuthnは以下のような流れになっています。
ブラウザ上で立ち上がるWebアプリから、登録/認証を行うためにボタンを押下する
- Webアプリから認証サーバにリクエストが送信される
- WebアプリからWebAuthn APIの呼び出しを行う
- ブラウザから認証器に生体認証がリクエストされる
- 認証器にて生体認証が行われる
- 認証器から生体認証の結果がブラウザに送られる
- WebアプリがWebAuthn APIのレスポンスとして認証結果を受け取る
- 認証サーバに認証結果が送信される
- Java(SpringBootベース)で開発されているライブラリ
- KeyCloakにも利用されているWebAuthnライブラリ
- TypeScript製のライブラリ
- JavaScript製のライブラリ
- Ruby製のライブラリ
- Go製のライブラリ
WebAuthnを実現するためには
WebAuthnを利用するためには、認証器・ブラウザ・認証サーバの3つが必要となります。
認証器及びブラウザはWebAuthnに対応しているものを利用する必要があります。
認証サーバについてもAttestationの検証やAssertionの検証(詳細は後述)などいくつかの機能を有している必要があります。
認証器のWebAuthn対応状況
Android、iOS、Windowsといった主要なデバイスがWebAuhtnに対応しています。
| OS | バージョン | 最新(2022年10月時点) |
|---|---|---|
| Android | Android 7.0以降 | Android 13 |
| iOS | iOS 14 以降 | iOS 16 |
| Windows | 19H1 以降 | 22H2 |
| MacOS | macOS 11.0 以降 | macOS 13 |
ブラウザのWebAuthn対応状況
主要な多くのブラウザがWebAuthnに対応しています。
| OS | ブラウザ |
|---|---|
| Android | Chrome、Edge、Firefox |
| iOS | Safari |
| Windows | Chrome、Edge、Firefox |
| MacOS | Safari |

WebAuthn 認証サーバを実現のために利用可能なOSS
認証サーバでは、Attestationの検証やAssertationの検証といった機能が必要となります。
それらの機能を実現するために、多くのOSSライブラリが公開されています。
| 名称 | 概要 | 参考(GitHubページ) |
|---|---|---|
| webauthn4j |
|
https://github.com/webauthn4j/webauthn4j |
| SimpleWebAuthn |
|
https://github.com/MasterKale/SimpleWebAuthn |
| fido2-lib |
|
https://github.com/webauthn-open-source/fido2-lib#readme |
| webauthn-ruby |
|
https://github.com/cedarcode/webauthn-ruby |
| WebAuthn Go library |
|
https://github.com/duo-labs/webauthn |
WebAuthnのシーケンス
WebAuthnには、登録/認証と2つのステップが存在します。
WebAuthnによって登録/認証がどのように行われるかをご紹介します。
登録
⓿ ブラウザから認証サーバーに登録リクエストを送信する
❶ 認証サーバーでチャレンジと呼ばれる文字列が生成され、サーバー情報とともにブラウザに送信される
❷ ❶の情報にユーザー情報を付加して、認証器に本人確認をリクエストする
❸ 認証器での本人確認が完了したら、公開鍵と秘密鍵のペアを作成する
❹ ❸で生成した秘密鍵で署名した署名情報、ユーザー情報、公開鍵をブラウザに送信する
❺ 認証サーバーに署名情報と公開鍵を送信し、DBに保存する

登録時の流れは上記の通りですが、では実際にはどんなパラメータをやり取りしているのでしょうか。
登録時にブラウザから実行されるAPIのリクエストとレスポンスのパラメータは次のようになっています。
リクエスト情報
登録時のリクエスト サンプル
{
"rp": {
"id": "localhost",
"name": "WebAuthn4J Spring Security Sample"
},
"user": {
"id": "NaaEsLhmOiXdnWIBoyUKEKgwRH82k3yGTxItVQLfEi8"
"name": "sample",
"displayName": "sample"
},
"challenge": "_hVwJhKnnxS33XuhurfAUzdgSeAakVdqmgz_419o78TUk0247gEMY51I26YDyqTG3NgSs"
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -257
},
{
"type": "public-key",
"alg": -7
}
],
"authenticatorSelection": {
"requireResidentKey": true
},
"attestation": "direct",
"extensions": {
"credProps": true
}
}
リクエストパラメータ項目の説明
| パラメータ | 内容 | |
|---|---|---|
| rp | 認証サーバの情報 | |
| id | 認証サーバを識別するためのID | |
| name | 認証サーバ名 | |
| user | 登録を行うユーザの情報 | |
| id | 登録を行うユーザを識別するためのID | |
| name | 登録を行うユーザの名称 | |
| displayName | ユーザの表示名称 | |
| challenge | 認証サーバが生成するランダムな文字列 | |
| pubKeyCredparams | 認証サーバがサポートするアルゴリズム | |
| type | public-key固定 | |
| alg | 認証サーバがサポートするアルゴリズム | |
| authenticatorSelection | 認証サーバが認証器に求める要件 | |
| requireResidentKey | 認証器のセキュリティキー内にユーザー情報を記録するオプション | |
| attestation | attestationをどのように認証サーバが受け取るかを示す このパラメータの値は、”none”, “indirect”, “direct”から選択可能可能 direct : 認証サーバは認証器で生成されたattestationをそのまま受け取る indirect : 認証サーバはattestationを受け取るが、Webアプリにて編集されたものでもよい none : attestationは不要 |
|
| extensions | Webアプリ、認証器に追加の処理を要求する場合に使う拡張領域 |
レスポンス情報
登録時のレスポンス サンプル
{
"authenticatorAttachment": "platform"
"id": "FafJLAJdfweECsR_rBLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"rawId": "axifjwlDKejLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"response":
{
"attestationObject": "o2NmbXRoZmlkbxxxxx-ODGsrPjUhg9gDkfa9J50mpMg2R05sLaI5P_rB1QNGxcI5BlV5M-xxxxx-aaaaaa)""
"clientDataJSON": "eyJjaGFsbGVuZ2UiOiJ1cFliNnNpYjlleEw3ZnZTZlFoSUVhek9rQmg4X1lKWFZQelN4MFQxNkIwIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo5MDAxIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9"
} ,
"type": "public-key"
}
レスポンスパラメータ項目の説明
| パラメータ | 内容 | |
|---|---|---|
| authenticatorAttachment | 認証器の接続状態を指定するための文字列 platform:ビルドインの認証器を表す cross-platform:外部接続の認証器を表す |
|
| id | rawIdをbase64urlエンコードしたもの | |
| rawId | 公開鍵を特定するためのID | |
| response | ||
| attestationObject | 詳細は後述 | |
| clientDataJSON | 認証サーバの情報などををBase64エンコードしたもの | |
| type | “public-key”固定 |
Attestation
認証器からはAttestation Objectと呼ばれる認証情報が返却されます。
Attestation Objectとは、認証器の情報・ユーザーの公開鍵・認証サーバーの情報・チャレンジを、認証器の秘密鍵で署名したものです。Attestation Object・デバイスの公開鍵・ユーザーの情報を認証サーバーへ送り、デバイスの公開鍵を用いてAttestation Objectが検証されます。Attestation Objectの正当性が確認できたら、ユーザーの公開鍵・ユーザーの情報がDBに保存され、登録処理が完了します。
WebAuthnのシーケンスをAttestation Objectを含めてまとめると下記のようになります。

認証
⓿ブラウザから認証サーバーに認証リクエストを送信する
❶認証サーバーでチャレンジと呼ばれる文字列が生成され、サーバー情報とともにブラウザに送信される
❷認証器に本人確認をリクエストする
❸サーバー情報を元にユーザーの一覧が表示され、ユーザーを選択して本人確認を行う
❹認証器での本人確認が完了したら、ユーザーの秘密鍵で署名した署名情報、ユーザー情報をブラウザに送信する
❺DBに登録されているユーザーの公開鍵で署名検証し、ユーザーを認証する

認証時の流れは上記の通りです。
次に認証時のAPIのリクエストとレスポンスのパラメータは次のようになっています。
リクエスト情報
認証時のリクエスト サンプル
{
"challenge": "_hVwJhKnnxS33XuhurfAUzdgSeAakVdqmgz_419o78TUk0247gEMY51I26YDyqTG3NgSs"
"rpId": "localhost",
"userVerification": "required",
"extensions": {}
}
リクエストパラメータ項目の説明
| パラメータ | 内容 |
|---|---|
| challenge | 認証サーバが生成するランダムな文字列 |
| rpId | 認証サーバを識別するためのID |
| userVerification | ユーザ認証を望むかを指定する。 required:必須 preferred: 必須でないが望む discouraged:不要 |
| extensions | Webアプリ、認証器に追加の処理を要求する場合に使う拡張領域 |
レスポンス情報
認証時のレスポンス サンプル
{
"id": "C3VNU4Wq0SEq4zVp7GPjRV0bIcscgM8nRr-m1hfD8o4",
"rawId": "axifjwlDKejLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"response": {
"credentialId": "C3VNU4Wq0SEq4zVp7GPjRV0bIcscgM8nRr-m1hfD8o4",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoibmh5NUV2M1lRUW1icXl4SGRZTnFHUSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MCIsImNyb3NzT3JpZ2luIjpmYWxzZSwib3RoZXJfa2V5c19jYW5fYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCJ9",
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAABg",
"signature": "l-6sRMnfxKnQXkQsutd9YDPaqYmnE2T4MpKak1ppLE-UX9PTk_HR3Lhio1_4I3agqDNASw7Uwh3imDxyb1Ruu0C7U-6OqMdUX3QqK7F8KVVVBe7fBHTPMxzBR8CdkCNP8R_YEs32_DtG2ROfauK9wmYBrpOEKue94UAMreEG1_2G-SH0ilinSYKISpDxZonX9co7I5Sgng2GGOdkeBAvj0_kvl3_eLcVLfIY0Rem2Dbm70_HaI2tlzIhqD2PnncHjnHWt3yEm02qlDy0wMpRiMW-ksqesokszNe81i3UKR9cqOVSxb1HzdmQy3Chb9JPeD-SejVQHtjQbiJZCe_Gwg",
"userHandler": "YWFhYWFhYWFhYWFh"
},
"type": "publick-key"
}
レスポンスパラメータ項目の説明
| パラメータ | 内容 | |
|---|---|---|
| id | rawIdをbase64urlエンコードしたもの | |
| rawId | 公開鍵を特定するためのID | |
| response | ||
| credentialId | 認証器自体が持つID | |
| clientData | 認証サーバの情報などををBase64エンコードしたもの | |
| authenticatorData | 認証器の情報 | |
| signature | Assertion Object 詳細は後述 | |
| userHandler | 認証器が生成したユーザ識別子 | |
| type | “public-key”固定 |
Assertion
認証時は認証器からAssertion Objectが返却されます。Assertion Objectとは認証サーバーの情報とチャレンジをユーザーの秘密鍵で署名したものです。登録時にDBに保存したユーザーの公開鍵を用いてAssertion Objectを検証することで、認証を行います。
WebAuthnのシーケンスをAssertion Objectを含めてまとめると下記のようになります。
まとめ
本記事では、WebAuthnという認証技術の概要や、WebAuthn APIのシーケンスを紹介しました。WebAuthnは、Webアプリでのパスワードレスな認証を実現する技術です。WebAuthnを利用することにより、ユーザは日々利用している複数のサイト、それぞれに対するパスワードを覚えておく必要がなくなります。
また、iPhoneとAndroidといった異なるプラットフォームの端末が共通のWebアプリで認証可能となるため、開発者はプラットフォーム毎に認証処理を開発する必要がなくなります。
しかし、WebAuthnには認証機を紛失してしまった場合リカバリができなかったり、端末を買い替えた際の認証情報の移行ができなかったり、いくつかの課題があります。
後半の記事では、WebAuthnの課題と課題を解決する技術であるPasskeyについてご紹介します。

