OAuth2サーバーの実装
今日はLibre OfficeのCalcで請求書を作って提出してみました。 雛形はすでにあったのでそこまで時間はかからなかったのですが、請求金額に線を引くだけなのに難しくて30分くらい無駄にしました。 これ別に必ずつけなきゃならないわけではないし、ちょっとしたアピールがしたいためにデジタルにアナログな表現を再現する必要ははたしてあったのだろうかと考えていました。
TL;DR ライブラリに頼るよりも、素直にライブラリのREADMEを見て順を追って実装していったほうが早い。
閑話休題。最近はOAuth2のサーバーをNode.jsで実装することになりました。
もともとの計画ではOAuth2の実装はExpress OAuth Serverというライブラリもあるので、これを利用すればサクッとOAuth2のサーバーが手に入ると思っていました。
金曜日に軽く下調べをして、実際の作業に着手したのは月曜日からだったのですが、まずインストールしてみて思ったのはnpm audit
でhighの脆弱性が2件ほど報告されていました。
それでGitHubのリポジトリの最終コミットの日付を見ると、今(2020年9月現在)からちょうど3年前に更新が止まっていました。
脆弱性はよくあるlodashが依存にある類なのですが、ずっと放置されていることを考えるとこのライブラリを使うのは得策ではありません。
READMEを見てみると、もともとはoauth2-serverというライブラリをラップしたもので、ほんの1ヶ月前にコミットがされていました。 Expressを使ってアプリケーションはたくさん作っていましたが、認証周り、特にOAuth2なんかは普段あまり気にしない領域なのでそこまで苦労したくないと思っていました。 それこそ、このラッパーのライブラリなのだから実装部分を真似れば簡単に実装できるだろうと思っていました。
READMEには幸いこのライブラリを使用したOAuth2.0 Exampleというリポジトリのリンクがありました。 また、このリポジトリはデータベースを利用していないので、先述のExpress OAuth Serverのリポジトリにあるexamplesのディレクトリを見るとちょうど私が使う予定のMongoDBだけでなくPostgreSQLなどの例もありました。 ただし残念ながらこの実装は古いバージョンなのでこの通りに実装しても動かないので泣きを見る羽目に。
oauth2-serverは現在v3で、これからv4のアップグレードが予定されているようなのですが、残念ながらOAuth2.0 Exampleはv2の実装が残っていたりします。 一見するとOAuth2.0 ExampleのREADMEは情報量が多くてよさそうなのですが、かみ砕くには時間がかかります。 また、oauth2-serverのREADMEも絶妙にわかりにくい。 ある程度見方がわかった今ならそれなりに理解できるのですが、ほぼほぼ知識ゼロの状態なので書いてあることが理解できませんでした。
当初はOAuth2.0 Exampleを見れば解決だと思っていたら、実はExpress OAuth Serverが依存に含まれていてそのままダウンロードして手元でテストを実行してもpassしません。
The example hangs on [ EXECUTING: Generate Authorization Code] #13というIssueでも報告されていますが、node_modules
の中にあるExpress OAuth Serverのoauth2-serverのmodel.js
を1行書き換えるだけなのですが、なかなか高度な修正です。
テストは無事通るようになるものの、このリポジトリは参考にならないのではないかと思うかもしれません。
実際の挙動やOAuth2の構成とか、FormDataの送信などとても参考になりました。
もしこのリポジトリを参考にしていなければ挫折していたか、あるいはもっと時間がかかったかもしれません。
最初はどうやって実装すればいいのか途方に暮れていましたが、ここでTypeScriptに救われると思いませんでした。 TypeScriptを使って本格的に実装するようになっておよそ1ヶ月経ちましたが、最初は面倒で苛つかされることも多かったものの、補完機能のおかげでまったく使ったことのないライブラリでも読み解くことができるようになりました。 ところでどうやって読み進めていったかというと、まずこのoauth2-serverは主に3つの関数しかありません。
APIのドキュメントにも書いてあるのですが、OAuth2Server
というクラスを作って、authorize()
でAuthorizationCode
を生成し、token()
という関数でAuthorizationCode
をrevokeしてToken
を生成します。
あとはこのToken
をもとにauthenticate()
を実行するとAccessToken
を発行できるようになるというのが大まかな流れです。
これらのメソッドを実行しようとする際にOAuth2Server
に渡したmodel
というオブジェクトにそれぞれ必要な関数がまとまっています。
Authorization Code Grantを参照すると、その際に定義する関数が結構あるので驚かされますが、さしあたって最初に実装しなければならない関数はgetClient()
とsaveAuthorizationCode()
のみです。
OAuth2.0 Exampleを参考にしながら、スタックトレースを眺めつつ実装していきました。
最終的には他の関数も必須のものを実装していくのですが、いきなりサンプルのコードを持ってくるよりも、時間はかかるもののひとつずつたどりながらの実装のほうが理解しやすかったです。
実装するにあたって注意が必要だったのはauth.js
の21行目付近でreq.body.user = {user: 1}
している箇所。
この一行を見逃していて最初のgetClient()
が正しく動くまでずいぶん苦労しました。
あとはredirect_uri
をテストで実装するのにnockでリダイレクトできるようにしたこと。
もちろんExpress OAuth Serverの実装は全く役に立たなかったわけではありません。
index.js
のhandleResponse
とhandleError
はそのまま引っ張ってきてもいいと思います。
AuthorizationCode
のリダイレクト処理や、AccessToken
などの表示は自分で実装しなくてもoauth2-serverが面倒みてくれました。
思った以上にとりとめのない内容になってしまいましたが、時系列で実装例までブログに残すのはさらに膨大になりそうなのでやめました。 今回の教訓は下手に近道を探すくらいならドキュメントをしっかり読んだほうが早かったということです。 ただ、こうして自分でもOAuth2のサーバーを実装できるようになったらいずれは自分のプロジェクトでも採用してみたい気もします。