👌

OIDCを利用した個人的なWEBシステムで認証情報を何をもって維持するのか考えてみた

2023/12/21に公開

個人開発を何度もしてきて、何一つとしてリリースしてない私が考えてみました。
リリースしないというセキュリティ担保では、やはり駄目だろう、と。
駄目なんだろうな、と思い、自分なりに整理してみました。

個人的な結論

結論から話せ、と世の中がいうので結論から言います。

  1. OIDCの処理はバックエンドで行えるならバックエンドで行う
  2. ブラウザから利用するWebシステムならCookieを使えば良い
  3. せっかくだしcookieにはJWTを入れておけばいい

個人的にはこんな感じでいいんじゃないかと思いました。
理由はここから先に記載します。興味があったら見てください。

セキュリティは難しいし、得意じゃないのでご指導ご鞭撻は優しくしてください。(祈り)

OIDCの処理はどこに置くのがいいのか

OIDC自体はブラウザで結果を受け取ることも、サーバで受け取ることも可能です。
ということは、まずそもそもどこで完結させるのかという悩みポイントがあります。

これはつまり、OIDCにどんな認証フローがあるのか? という話と同義です(たぶん)
で、どんなフローがあるかというと以下のようなものがあるようです。

  1. 認可コードフロー
  2. Implicitフロー
  3. ハイブリッドフロー

名前が違うように、それぞれちょっとずつ動きが違います。
詳細は例えばこちらのコンテンツが分かりやすいと思います。
ついでに話は変わりますが国もなんか出してます

OIDCの詳細はボロが出るし、説明できるほど詳しくないので省略しますが
今回は 認可コードフロー が適していそうです。

サーバ側ですべて処理できるのでフロントにTokenなどの秘匿したい情報を露出する必要がなくて一番安全になります。

というわけで、整理すると

  • OIDCの処理はサーバに置く
  • IDToken, AccessTokenはフロントに見せずにバックエンドに隠しておく

というのが良さそうでした。

システムの認証ってどっちの認証?

余談ですが、僕は色々調べてるうちに混乱してしまったので
どんな混乱をしたのか記載しておきます。

ご存知の通りOIDCは認証ですよね。
で、自分のシステムのログイン機能も認証を行いますね。

じゃあOIDCを使って認証をするっていうのは、OIDCの認証状態を使って自分のシステムの認証をすることなんでしょうか?
認証Providerへの認証が通っており、ログインが維持できていることは何を持って把握するんでしょう?
IDTokenはもらえるけどそれはあくまでトークンで、Providerに対してのセッションってなんなんでしょう??

そういう混乱をしました。何を言ってるんでしょうね。
冷静になると、何を言ってるんだ、となります。

OIDCのProviderに対する認証は、Providerへの認証であって自分のシステムとクライアント間の認証ではないんですよね。
語弊があるかもしれないですが、あくまでログイン機能をProviderに移譲してるだけなのでそれが成功したあとにどうするかは普通のWEBシステムと変わらないです。

認証状態を維持したいならセッションを張るなりする処理を自分のシステム側で実装して、OIDCで得られた情報を使うのではなくて自分のシステムで状態を管理して普通にWEBシステムをすればいいのです。

という理解に落ち着きました。

さてOIDCをサーバ側で行うということが決まったので、じゃあ次はログインしたあとの状態管理です。
どうやってログイン状態を管理するかを検討する必要があります。

ここでもう擦られすぎて炭になってそうなcookie or jwtを考えていく必要があります。
そのために一度それぞれについて整理してみたいと思います。

単純にcookieと言いたいところですがCookieを使うのであればセットでSessionIDを使うことがほとんどだと思うので、組み合わせて考えてみます。

cookieを使う場合の特徴は以下です

  • ブラウザに手厚くサポートされており、設定次第で最もセキュアな保存場所にすることができる
    • 例えばCSRFの危険性などはあるが、SameSite指定をしておけば基本問題ない
    • httpOnlyにすることでjsから操作されることはない、など
  • SessionIDが流出した場合に「特定のユーザのみ」の認証状態破棄が行える
  • SessionIDとユーザ情報の紐付けを管理するためのストレージが必要になる (redisだったりDBだったり)
  • スケールさせる際にストレージに対する考慮や処理が必要
  • ログアウト機能が簡単に作れる

JWTとCookieの比較でこれらについてよく言われる話もありますが、それに対する個人的なお気持ちは後述します。

JWT

主にJWTトークンを利用し、Bearerにセットしやり取りとかするやつです。
トークンの状態により認証状態を管理します。

この方式の特徴は以下です。

  • Sessionが流出した際に該当のトークンのみを破棄することができない
  • 保存先にlocalstorageを選ぶとcookieと比較してセキュアでない
  • 保存先にcookieを選んでもhttpOnlyにできない
  • Token自体に有効期限などの情報があるため、セッション用のストレージが不要
  • スケールさせやすい
  • アプリではcookieによる認証管理が行えないため、アプリ/Webで同じ認証基盤使いたいときには便利。
  • ログアウト機能を作るのが大変

有名な話ですがログアウト機能では、クライアントが使っているJWTを破棄するだけでは駄目です。
破棄したいJWT自体を管理して、同じJWTで問い合わせがあったときにそれを弾くことができないといけません。
OWASPにそうかかれるようになったそうなので、ちゃんとやらないとなのです。

そしてそこまでやる場合、Sessionストレージにアクセスしなくていいというメリットで浮いていたパフォーマンスの優位性が結局DBなどのストレージに検証にいかないといけないという手間で相殺されている気がします。
スケールしやすさはそのまま残ってる気がしますが。

よくいわれる説について

cookie, jwtの比較をする前に、よく言われる双方に対する説を個人的に考えてみました

JWTは流出したときに問題が多い

すごくよく言われるこの説です。

分解すると以下の2つで言われてることが多い印象です

  1. JWTはセッションが破棄できない
  2. JWTは流出時に問題が大きくなる

どっちも結局同じことを言ってるのですが、それぞれちょっと誇張しすぎに感じています (個人の感想)

1については多分言葉足らずなだけなのですが、鍵を更新すれば全部のセッションが破棄されるので破棄はできます。
ただ管理システムの機能として作成しようとすると、ものすんごく大変ですし、個別のユーザに対して破棄ができないのでSessionIDによる管理より不便です

2についても嘘ではないと思ってますが、ちょっと圧が強いなぁと感じてるタイプです。

これは考えるときにまず問いかけが必要な気はしています。
例えば以下みたいな問いかけです。

1: あなたの運用しているシステムはセッションハイジャックやセッション情報の漏出を検知できますか?
2: セッション情報が流出したときに、個別のユーザだけ認証情報を破棄したいですか?

このあたりは必要なのかなと思います。
このどちらかが「yes」となる場合に、JWTのセッションが破棄できないことが真面目に考えるべき問題になりそうだとはおもいます。

そして個人開発の段階だと、これは別に気にしなくていいかなという優先度になるなと感じます。
問題になるところまでシステムが成長してからの改修でよさそうです。
そのときは少し大変だし、認証基盤を入れ替えたときに全員強制ログアウトになりますが、そこまで個人システムが育ってたら十分やる価値あるでしょ、という。

業務システムでも、もしセッションの流出が検知できるようなシステムでないのであれば、問題に気づくのはユーザからの問い合わせなど外からの動きによることが多いと思われます。
その頃には問題実際に発生し始めたときから、多くの時間が経過しているでしょう。

そうなると新しい秘密鍵と公開鍵のセットを作成して、システムに反映するまでの時間が致命的な問題になるとは考えづらいのかなと思いました。
もう致命傷を受けてるから...

またセッションが流出してるの気づいたときって、もうそのシステムが脆弱な状態なので誰か個別に破棄するのではなくて
全員破棄したほうが良いし、システム止めたほうが良かったりするケースが多いんじゃないかなと思いました。

とはいえ、扱う情報や要求されるセキュリティによっては問題になるので、そこは都度考える必要がありそうですね。

双方比較した個人的な感想

と、2つの特徴を見てみたところで自分の中では以下にするのが良いんじゃないかなぁとなりました。

  • SessionIDを使ったセッション管理をする
  • cookieにはSessionID直接ではなくJWTとして持たせてしまう
  • JWTを検証し問題なければsession storageを引く
  • セッションの破棄はsession storageから値を削除

これが良いと思った理由は、結局適切に属性を設定したCookieが一番普通のWebシステムだとセキュアだからです。
あと今更なんですが、実はWEBだけのシステムであれば別にJWTをBearerに入れる意味がないというシンプルな話でした。

またCookie vs JWTとよくなるんですが、別にこの二人は本質的には対立関係にないじゃん、ということも思いました。
認証情報の保持場所、送り方という話と状態の管理の話なんですよね。
そこでよく使われてるのがcookie, jwtというだけであって。

なのでJWTの持つ

  • payloadに色々入れられる
  • 改善検知できる

という特徴は便利だし、とりあえず使っとけばいいんじゃない? という気持ちになりました。

以下、簡単にですがこの構成の自分が感じたメリット・デメリットを記載します。

メリット

  • JWTの検証により不正なアクセス・いたずらアクセスをstorageにアクセスせずに破棄できる
  • payloadに一定の情報を追加することでDBアクセスなどを減らせる(ことがある)
  • 変更検知が機能する (session idのブルートフォースみたいなので突破されない)

デメリット

  • JWTを検証に一定の計算コストはかかる
  • しょぼいシステムを作るにはやや頑張りすぎ

まとめ

  • OIDC処理はサーバで処理
  • 認証状態はCookie with Sessionで管理

これが今回の結論になりました。

実はこれ、1年に1回くらい自分でよくわからなくなって、毎回同じようなことを考えて同じような結論に至っています。
でも毎回混乱してよくわからなくなるので、未来の自分のために書き下ろして、せっかくだしコンテンツにしてみました。

多分結構ツッコミどころが多い内容なんだろうなと思うので、ぜひお気兼ねなく、でもできるだけ優しく指摘してもらえると嬉しいです。

余談ですが、過去なにか作ろうとしたときには、まぁどうせ自分のシステムなんて誰も使わんだろうからJWTでいいや、ってなるときもありました。
※ 別にJWTが悪いわけではありません。要件とシステムの持っている情報次第だと思います。

Discussion