フューチャー技術ブログ

パスワードレスな認証を実現する認証ミドルウェアのhanko

名前からすると日本の古き良き(悪名高い)デバイス認証方式のあれのように見えますが、パスワードレスな認証(passkey)を実現するOSSのプロダクトです。Go製でライセンスはAGPL3です。なかなか面白そうなので動かしてみました。

https://www.hanko.io/

このhankoのメンバーが運営しているpasskeys.ioというウェブサイトもあり、パスワードレスなログインを広めていこう! という啓蒙サイトになっています。

https://www.passkeys.io/

この↑のサイトの存在を知らなかったのですが、@takuan_oshoさんからタレコミをいただきました。ありがとうございます。

動かし方

READMEに書いてある通りにdocker composeで一通り必要なものを起動します。

$ docker compose -f deploy/docker-compose/quickstart.yaml
-p "hanko-quickstart" up --build

デモサーバーが8888で起動するのでブラウザでアクセスしてみます。登録アプリでは最終的に秘密ページ(secured.html)を表示しているのですが、そこに至るまでのフローがいろいろ選べます。

ユーザー登録をする(パスキーの登録あり、なし)フローと、登録後にログイン(メールに送られてくる6桁コード or 指紋認証)でした。AndroidのPixel 4aとmacでは指紋認証でしたが、きっとWindowsだと顔認証とかも機種によって選べるのかもしれません。手元のZephyrus G14はカメラ無しなので試せませんでしたが。

hanko.jpg

hanko自身はパスワード認証にも対応しているのですが、そのやり方はちょっとわからなかったです。まあhankoの目玉機能は一通り試せた感じです。YubiKeyを入れてみたけど登録手段としては表示されませんでした。READMEによるとまだYubiKey対応は開発中のようですね。

docker composeのシステム構成

サンプルプロジェクトのdocker-composeを見ると結構たくさんコンポーネントを使っています。こんなに全部必要なのか? みたいに思ったので軽くみてみました。

  • hanko-migrate
  • hanko
  • postgresd
  • hankojs
  • example
  • mailslurper

このうち、hanko-migrateとhankoは同じイメージのオプション違いです。hankoサーバーがmigrateオプションを付けるとDBマイグレーションが起動するような仕組みになっているようです。DBマイグレーションで使っているライブラリは以下のやつでした。

postgresdはお馴染みのPostgreSQLです。対応しているデータベースは以下の4つです。

  • CockroachDB
  • MariaDB
  • MySQL
  • PostgreSQL

hankojsは<hanko-auth/>というカスタムタグのJavaScriptライブラリをビルドして提供するためのサービスとなっています。アプリ開発する場合はnpm install @teamhanko/hanko-elementsすればいいので、必須コンポーネントというわけではなさそうです。カスタムタグなのでLitを使っているのかと思ってコードを見てみたらpreact-custom-elementを使っていました。preact製でちょっと残念。マイクロフロントエンド的にカスタムタグを使うのは面白いですね。

mailslurperは開発時に便利に使えるメールサーバー&クライアントでした。今まではメール機能の開発は面倒なものだと思っていましたがこれは便利ですね。上記のフローで6桁のコードが送られてくる時に、localhost:8080にアクセスするとメール一覧のビューアーが表示されるのでここで6桁コードが取得できます。

https://www.mailslurper.com/

スクリーンショット_2022-08-15_17.43.41.png

いろいろコンポーネントがありましたが、JSはウェブフロントエンドにライブラリをバンドルしちゃえば不要ですし、DBとメールサーバーは本番環境ではSaaSサービスを使うでしょうし、マイグレーションは踏み台から起動すればいいだけなので、本番環境で起動すべきはhankoのバックエンドサーバーとアプリケーション本体だけでいけそうですね。

サンプルサーバーの実装

exampleはサンプルアプリケーションの本体です。ほとんど素のHTTPサーバーですが、ミドルウェアを1つ持っています。jwks.jsonをhanko本体のバックエンドサーバーから取得してきて認証の確認をしています。

func SessionMiddleware() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
cookie, err := c.Cookie("hanko")
if err == http.ErrNoCookie {
return c.Redirect(http.StatusTemporaryRedirect, "/unauthorized")
}
if err != nil {
return err
}
set, err := jwk.Fetch(context.Background(), "http://hanko:8000/.well-known/jwks.json")
if err != nil {
return err
}

token, err := jwt.Parse([]byte(cookie.Value), jwt.WithKeySet(set))
if err != nil {
return c.Redirect(http.StatusTemporaryRedirect, "/unauthorized")
}

log.Printf("session for user '%s' verified successfully", token.Subject())

return next(c)
}
}
}

docker-compose.yamlのテクニック

今回サンプルを見てみて面白かったのはdocker-composeの書き方ですね。DBの待ち合わせとかどうしようか、リトライしておけばOK?みたいに今までやっていたのですが、このサンプルの書き方は良いですね。依存の順番に並べ替えて該当箇所だけ抜き出したのが以下のサンプルになります。

/deploy/docker-compose/quickstart.yaml
postgresd:
healthcheck:
test: pg_isready -U hanko -d hanko
interval: 10s
timeout: 10s
retries: 3
start_period: 30s
hanko-migrate:
restart: on-failure
depends_on:
postgresd:
condition: service_healthy
hanko:
restart: unless-stopped
depends_on:
hanko-migrate:
condition: service_completed_successfully

まずpostgresdではヘルスチェックを設定しています。DBマイグレーションはservice_healthyのpostgresdに依存、という書き方になっています。こういう書き方が可能なんですね。バックエンド本体はマイグレーションがservice_completed_successfullyの場合に起動となっています。マイグレーションと本体でそれぞれ一度起動なのかバッチなのかという違いがあるのでrestartの書き方が変えてあります。

まとめ

まだベータということですが、hankoを動かして、ちょっとコードを読んでみました。

パスワードレスなログインは今後はエンタープライズなシステムでも主流になっていくと思いますが、それをコンパクトに実装したhankoは良い学習素材になってくれそうです。

本番運用で使うには、ユーザー登録の管理画面とかいろいろ周りに用意してあげないといけない気もします。また、本番環境ではAuth0などのSaaSを使ってローカル開発環境ではこちら、みたいな使い分けとかもありかもしれませんが、そのような組み合わせの実現にもいろいろノウハウは必要そうですので、サンプルの起動は簡単でも導入にはちょっと工夫が必要な気がします。

また、新規に新しく作られているプロダクトはいろいろモダンなテクニックを知るのにも良いですね。特にdocker-compose.yaml周りの知識のアップデートになりました。