SPAとバックエンドの認証の話

このあたりは何がベストなんだろう、と常々考えていて、今回このアンケートを見ていいきっかけだと思ったので普段考えていることを吐き出しておく

まず分類

最初に以下の軸で分類する必要があると思う

  1. そのAPIは3rdパーティに公開する可能性があるか
  2. ユーザー管理はそのサービスだけで閉じているのか

3rdパーティに公開する可能性があるか

3rdパーティに公開する可能性がある場合、APIはOAuth2のリソースサーバーとして振る舞い、アクセストークンで保護する

その際、SPAアプリにアクセストークンやリフレッシュトークンを持たせたくないので、別途OAuth2のクライアントの役割をするバックエンドが欲しい(BFFと言っていいのか?)
SPAとBFFの間はcookieで繋ぐという形にする
( SPA -cookie-> BFF -access-token-> API
BFFはcookieに紐づけてアクセストークンやリフレッシュトークンを管理する

別にBFFとAPIは同じアプリケーションにまとめてもいいと思う

アクセストークンが必要なので、認証にはOAuth2の認可コードフローを使うことになる

ユーザー管理がそのサービス内で閉じない場合

これは

  • 別のサービスも公開していて(あるいは今後その予定)、そちらとIDを統合したい
  • ソーシャルログインしたい
  • 自分でパスワードとかを保有したくない(外部のIDaaSに任せたい)

などのパターンがあると思う

この場合OIDCを使って、認証は外部に移譲する
バックエンド側はOAuth2でいうところのクライアントの役割を担う

バックエンドは認可コードフローでIDトークンを受け取り、SPAとの間のセッションはcookieで維持する

両方に当てはまる場合

上2つを組み合わせる
( SPA -cookie-> BFF -access-token-> API
BFF側で認証時にIDトークンの検証を追加するだけ

どちらでもない場合

上記どちらにも当てはまらない場合は、OAuth2やOIDCを採用するのは過剰に感じる
ID/パスワードを受け付けてcookieを発行するエンドポイントをAPIに用意すればいいと思う

cookieについて

どのパターンでもcookieを使っているが、色々考えた結果cookieが最高、という結論に落ち着いた
結局SPAとバックエンドの間のセッションは自前で維持しなくてはいけないわけで、そのために毎回API実行時になにかしらのトークンを付与しないといけない
HTTPヘッダーにつけるのも選択肢なのだが、以下の3点でcookieに比べて劣位があると感じている

  • cookieならブラウザが自動でつけてくれる
  • javascriptから見れない(http only)のでXSSされても抜き取られない
  • 最初の受け渡しに困らない

特に3つ目が悩みポイントで、cookieを使わない場合、バックエンドからSPAへのトークンの受け渡しは

  • index.htmlとかにレンダリングしちゃう
  • SPAにリダイレクトする際にquery parameterで渡す

ぐらいしか方法が思いつかない

前者だとnextjsとか使ってSSR必須になるし、後者だとimplicitフローの問題点ふたたび、という感じがある
かといってそこをガチガチに固めていくと結局SPA側をクライアントにするのと何も変わらない、ということになる

さて、cookieを使おうとすると、SPAとバックエンドを同じドメインで配信しないといけない
以下の選択肢がとれる

  • バックエンドにSPAアプリも同梱する
  • 前段にwebサーバーをおいて特定のパス(/api/* など)だけバックエンドに流す

先日こちらもアンケートになっていた

別でデプロイはしたいんだけど、ドメインは同じにしたいので、個人的には後者を推す
webサーバーと書いたが、AWSで構築するならCloudFrontを使う

SPAと認可コードフローとcookie

じゃあSPAとバックエンドで認可コードフローってどんな感じにやるの?というのが次の問題になると思う
特にバックエンドにSPAを同梱しない場合

SPA, バックエンドそれぞれの視点でこんなことをすればいいと思っている

バックエンド視点

  • cookie検証用のエンドポイントを用意する
  • 認証開始エンドポイントを用意する
  • 認証開始エンドポイントが呼ばれたら認可コードフローを開始し、認証が完了したらSPAにリダイレクトする

SPA視点

  • ロードされたらcookie検証用エンドポイントを叩く
  • 401ならバックエンドの認証開始エンドポイントにリダイレクトする
  • 認証済みなら画面をレンダリングする


以上!