仕組みから学ぶWebAuthn対応Djangoアプリ開発

Ryuji Tsutsui
CreditEngine Tech
Published in
10 min readDec 11, 2018

こんにちは、筒井です。
Qiita Django Advent Calendar 2018 11日目は、WebAuthn(Web Authentication)認証に対応したDjangoアプリケーションの作り方について解説します。

WebAuthnとは何か

WebAuthn(Web Authentication)とは、2018年4月10日にW3C勧告候補になったウェブ認証の仕組みです。従来は本人しか知らない前提の「パスワード」による認証が主流でしたが、以下のリスクがありました。

  • 推測されやすい単純な値を使っている場合にアカウントを盗まれる。
  • 他のサービスと同じID・パスワードを使いまわしていると、あるサービスでパスワードが漏洩することで他のサービスでもアカウントを盗まれる。

パスワード認証の弱点を補うためによく使われる手段として、1Passwordのようなパスワード生成ツールや2段階認証がありますが、いずれも手続きが複雑になり、万人向けとは言えません。

WebAuthnでは「認証器(Authenticator)」と呼ばれるデバイスを利用することで、パスワード認証より人間の負担を減らし、かつよりセキュアに認証を行います。以下で登録・認証の流れを説明します。

【登録】
Relying Party Server(本記事ではDjangoアプリケーションがここに該当します)から生成されたPublicKeyCredentialCreateOptionsをJavaScriptコードが受け取り、WebAuthnAPI経由で公開鍵を生成・サーバーに送信し、サーバーが公開鍵を検証することで登録が完了します。
以下に全体の流れを説明する図を作りました。

WebAuthnでのアカウント登録の仕組み(「Web Authentication: An API for accessing Public Key Credentials Level 1」5. Web Authentication API Figure 1を参考に作成)

【認証】

サーバーが生成したPublicKeyCredentialRequestOptionsを受け取ったJavaScriptコードはWebAuthnAPI経由で認証情報を生成・サーバーに送信し、受け取ったサーバーは公開鍵を使って認証情報を検証します。
以下に全体の流れを説明する図を作りました。

WebAuthnで認証の仕組み(「Web Authentication: An API for accessing Public Key Credentials Level 1」5. Web Authentication API Figure 2を参考に作成)

WebAuthnをサポートするブラウザについては以下を参照してください。

Web Authentication API | MDN ブラウザーの対応

最近のブラウザであれば大抵サポートしているようです(2018年12月11日現在、iOS版Safariが未サポートなのはちょっと困りますね…)。

また、上記サイトには載っていませんが、Windows 10 build 17723からはMicrosoft Edgeもサポートしています。

DjangoでWebAuthnを導入してみる

では、実際にWebAuthn対応のDjangoアプリケーションを作ってみましょう。今回は、認証器としてYubico セキュリティキーを用意しました。

「Yubico セキュリティキー」の写真

Pythonのバージョンは3.6系を使っています。
使用するPythonフレームワーク・ライブラリのバージョンは以下のとおりです。
djangorestframework・django-extensionsはDjango設定ファイルの変更が必要ですが、詳細は以下にリンクしたドキュメントのInstallationを参照してください。

djangorestframeworkは登録・認証用のAPIを作成するために使います。
また、WebAuthnAPIはローカル開発であってもhttpsを使う必要があるため、django-extensionsの runserver_plus コマンドを--cert-fileオプション付きで使います。pyOpenSSL・Werkzeugは runserver_plus に必要なライブラリです。
前述の登録・認証処理を楽に書けるようにfido2も使います。

登録・認証周りのコードはusers というDjangoアプリケーションに書いていきます。
モデル定義は以下のとおりです。

モデル定義

AUTH_USER_MODEL に設定する User モデルと、WebAuthn公開鍵を管理する WebAuthnPublicKey モデルを定義しています。1ユーザーが認証器を複数持っている場合を考慮して、1対多の関係にしています。
WebAuthnPublicKeyManager に定義した credentialsメソッドの用途は後で説明するので、とりあえず読み飛ばしてください。

まず登録処理を実装します。サーバーサイドのコードは以下のとおりです。

登録処理(サーバーサイド)

WebAuthn認証ではサーバーとのデータのやり取りをCBOR形式で行う必要がありますが、Django REST frameworkでは未対応のため、専用のParser・Rendererを作っています。

RegisterBeginViewSet (PublicKeyCredentialCreateOptionsを返すためのAPI用のビュー)では、前述の credentials メソッドを呼んで、過去に同じ username に紐づけた認証器を登録できないようにしています。

また、 views.py の中でセッションに challengeを入れていますが、デフォルトの SESSION_SERIALIZER だと 「TypeError: Object of type ‘bytes’ is not JSON serializable」エラーになるため、django.contrib.sessions.serializers.PickleSerializer を使うようにしています。

クライアントサイドのコードは以下のとおりです。

登録処理(クライアントサイド)

これで登録できるようになりました。
./manage.py runserver_plus localhost:8000 --cert-file cert.crt で開発サーバーを立ち上げて、WebAuthnをサポートするブラウザでhttps://localhost:8000/users/signup/ を開いてください。
「ユーザー名」・「表示名」を入力して「サインアップ」をクリックすると、認証器を要求するダイアログが表示されます。以下はMac版Chrome71.0.3578.80で動作確認した際の画面です。

Mac版Chrome71.0.3578.80で動作確認した際の画面

USBポートにYubico セキュリティキーを刺すと鍵のアイコンが点滅します。そこを指で触ると登録完了です。

USBポートにYubico セキュリティキーを刺した直後

次に、認証処理を実装します。サーバーサイドのコードは以下のとおりです(Parser・Renderer・Permissionは前述と同じコードを使っているので省略)。

認証処理(サーバーサイド)

views.py の最後の行 reverse('accounts:profile') に該当するビュー・テンプレートの定義は省略します。ログインできたことが分かるように、テンプレートの中で {{ request.user.display_name }} を書いておけば内容は何でもいいです。

WebAuthnに対応した認証バックエンドとして users/backends.pyWebAuthnBackend を作成しています。 settings.py には以下のように書いておく必要があります。

AUTHENTICATION_BACKENDS = (
'users.backends.WebAuthnBackend',
)

クライアントサイドのコードは以下のとおりです。

認証処理(クライアントサイド)

「ユーザー名」を入力して「サインイン」をクリックすると、登録と同じく認証器を要求するダイアログが表示されます。

Mac版Chrome71.0.3578.80で動作確認した際の画面

認証が通ると request.user でユーザー情報を参照できるようになります。

認証が通った後の画面

これで、WebAuthn対応のDjangoアプリケーションに最低限必要な機能の実装はできました。実際にサービスで使うには、以下の機能についても実装しておいたほうがいいでしょう。

  • WebAuthn非サポート環境でも使える認証方法の提供
  • 認証器を紛失した際に使う認証器再登録のフローの提供

また、公開鍵は生成時のドメインでしか使えません。
もし、公開中のウェブサービスのドメインを変更すると、旧ドメインのユーザーは突然ログインできなくなってしまいます。旧ドメインで登録した公開鍵がある場合に再登録を促すフローも用意しておくと、より親切なサービスになるでしょう。

参考資料

--

--

Ryuji Tsutsui
CreditEngine Tech

私がmediumに記事を書くことはおそらく当分ないので、 https://ryu22e.org/ の方を読んでください。