概要
Webアプリで「SPAフロントエンド+APIバックエンド」のアーキテクチャを採用する際、認証と認可の役割や処理の分担について整理することを目的とする。
「認証」は「アクセスしている人は誰か?」という観点から比較的イメージしやすいが、「認可」はやや掴みにくい場面もあるだろう。具体的にはマイクロサービスアーキテクチャ(以下「MS」と略す)において、次のように責任を分離して構成する場合を想定する。
- UIはフロントエンド、
- アプリの機能提供はバックエンド、
- 認証の管理は認証サーバー、
- 認可の管理は認可サーバー、
この構成において、「なぜ認可の管理が必要なのか?」「どのような方法・プロトコルが一般的に採用されているか?」を整理する。
なお後者の「推奨プロトコル」について先に結論を言うと、次のとおりである。
- 認証プロトコルとして OpenID Connect 1.0(以下 OIDC)
- 認可プロトコルとして OAuth 2.0
これらが推奨である理由は「そういう場面に適するように策定されたプロトコルだから」である。
本記事では、「そのような構成において、どのような点を考慮すべきか」という整理に留め、プロトコルの詳細な仕様説明までは踏み込まない。
本記事は筆者の整理用メモであるが、同様の疑問を持つ人の参考になれば幸いである。
認証と認可がなぜ必要か?
「SPAフロントエンド+APIバックエンド」構成のWebアプリでは、Webアプリをする利用するユーザー(以降、「ユーザー」と略記)のアクセス経路は以下のようになる。
ユーザーはブラウザからSPAフロントエンドにアクセスし、そのフロントエンドはAPIバックエンドに対してリクエストを送る。
このときAPIバックエンドは、「このアクセスは誰からのものか?」を判断する必要がある。これは、なりすましを防ぐという意味で基本的な要件であり、異論の余地は少ない。
なりしましとは、悪意のある第三者が正規ユーザーを装ってアクセスしてくる場合などで、図示すると以下のようになる。
ここで問題になるのは、「なりすまし」のリスクはユーザーだけに留まらず、フロントエンドにも存在するという点である。
つまり、ユーザー自身は正規のものであっても、そのユーザーがアクセスしているフロントエンドが偽物だった場合、
バックエンドがデータをそのまま渡してしまうことで、情報が不正に取得されてしまうリスクがある。
その場合の構成を図にすると以下のようになる。
こうしたリスクを防ぐには、「誰が(ユーザー)」「どこから(フロントエンド)」アクセスしているかを確認し、信頼できる相手にだけ応答する仕組みが必要になる。
そのために、認証サーバーと認可サーバーを用いる。
認証と認可の処理をバックエンドが兼ねる構成もあるが、責任分担(Separation of Concerns)の観点からは、バックエンドとは別のリソースに分離する方が望ましい。
これは、アプリケーション本体(APIバックエンド)が「アプリの業務機能の提供」に集中し、ユーザーの識別やアクセス制御のようなセキュリティ領域は専用のサービスに委ねることで、それぞれの役割を明確にし、保守性・セキュリティ・拡張性を高めるためである。
なお、認証と認可の機能自体は、ひとつのサービスに統合して提供されることが一般的である(たとえば AWS Cognitoや、Azure Microsoft ID プラットフォーム1 2)。
これは、認証(ユーザーが誰か)と認可(そのユーザーにどのリソースを通したアクセスを許可するか)が密接に関連する処理であり、同じユーザーディレクトリやトークン管理を扱うため、一元的に設定・運用・監視することにより利便性と整合性が高まるためだ。
このように、「どのコンポーネントがどの責任を担うか」というレベルでは分離しつつ、密接に関係する機能は同一リソースで統合管理するという設計が、現実的かつ実用的な選択肢となっている。
なお、本記事ではあくまで「機能の役割を区別する」ことに主眼を置くため、認証と認可は別リソースとして記述を続ける。
認証サーバーとバックエンドの通信で求めらえること
先の図で示したように、バックエンドは「この人は誰か?」と認証サーバーに問い合わせ、認証サーバーが「Aさんだ」と回答する。
この通信においては、その回答がすり替えられたり、偽造されたりすることなく、正しく受け取れることが求められる。
例えるなら、本人確認書類を受け取る際に、封筒が途中ですり替えられていないか、封が破られていないかを確認するようなものだ。
こうした信頼性を担保するために用いられるのが、OpenID Connect 1.0(OIDC)3である。
OIDCでは、署名付きIDトークンや「誰宛てのトークンか」(audパラメータ)といった仕組みにより、応答の正当性を検証できる。
認可サーバーとフロントエンドの通信で求められること
先の図で示したように、
フロントエンドは「私は正規のフロントエンドです。バックエンドにアクセスしたいので許可(アクセストークン)をください」と認可サーバーに要求する。
認可サーバーは「このユーザーにはこのリソースへのアクセス権がある」と判断して、アクセストークンを発行する。
この通信では、その要求が本当に正規のフロントエンドからのものであるかを見分ける必要がある。
例えるなら、あるホテルが「公式サイトからの予約だけ割引します」としていたとして、偽サイトの予約も割引してしまったら困る――という状況に似ている。
こうした「偽サイトによるなりすまし」を防ぐためのプロトコルがOAuth 2.04である5。
OAuth 2.0では、リダイレクトURIの事前登録やPKCE(Proof Key for Code Exchange)などを通じて、「本当に正しいサイト(フロントエンド)からの要求かどうか」を確認できるようになっている。
なお、実際には、偽サイトを作成してまで攻撃を試みるケースは少ないため、多くの場合はクライアント検証(要求元フロントエンドの真正性)を省略(=OAuth2.0を採用しない)しても問題にならないと考えられる6。システム特性や想定脅威に応じて、リスクを評価し判断することになる。
フロントエンドと秘密鍵の話
先ほど、認証サーバーとバックエンドの通信では「ユーザーが誰であるか」という情報が正しく受け取れる必要があると述べたが、実際にはそれだけでなく、「その情報を要求しているバックエンドが本当に正当なものかどうか」も重要な判断基準となる。
つまり、認証においても、認可と同様に「クライアントが正規かどうか」という観点が求められる。
先の節でそこに触れなかったのは、バックエンドは多くの場合に「秘密鍵(秘密のサインペン)」のようなものを安全に保持できるからである。
この秘密鍵により「これは自分が作成したリクエストである」というサインを付けることができ、認証サーバー側でもそれを検証できるため、説明を省略した。
しかし、フロントエンドは事情が異なる。フロントエンドのコードはブラウザにダウンロードされるため、誰でも中身を確認でき、秘密鍵も容易に複製できてしまう。
したがって、秘密鍵に基づく認証は成立しない。
そのため、フロントエンドには別のアプローチ、たとえばPKCEなどの仕組みを使って「正規のフロントエンドからのリクエストである」ことを証明する必要がある。
OIDC(認証)もOAuth 2.0(認可)も、こうした「秘密鍵を持てないクライアント」を想定した対応策を定めている。
しかし、このあたりの詳細はプロトコル自体の解説になってくるため、本記事では触れないものとする。
SPAではなく従来のサーバーサイドレンダリング型だと?
最後に、比較として、
サーバーサイドレンダリング型の従来型Webアプリケーション構成(MPA構成)の場合を見てみよう。
MPA構成では、ユーザーのアクセスの流れは以下のとおりである。
ユーザーはブラウザを通じて、Webアプリケーション本体(アプリケーションサーバー)に直接アクセスする。
サーバーは、受け取ったリクエストに対して「このリクエストは誰によるものか?」という認証判断を行う。
この点はSPA構成と同様である。
異なるのは、MPA構成ではすべての通信がアプリケーションサーバーを通して行われるため、
「どのフロントエンドからのリクエストか?」といった判定が不要になるということだ。
つまり、MPAでは「利用者のなりすまし」にのみ対処すればよい。
認証と認可の観点では、このようにMPA構成がシンプルになるが、一方で、可用性やスケーラビリティといった観点では、SPAフロントエンドとAPIバックエンドを分離した構成のほうが優れている場合が多い。
要件によって「認証/認可のシンプルさ」を重視するか、「拡張性・運用性」を重視するかを見極め、最適なアーキテクチャを選択することが重要となるだろう。
おわりに
本記事では、SPA+API構成において「なぜ認証と認可の両方が必要か」を中心に整理し、
参考としてMPA構成との違いも対比してみた。
-
SPA+API構成:
認証に加えて「リクエスト元(クライアント)」の識別も必要となり、設計は複雑になるが、
可用性・スケーラビリティ・疎結合性に優れる7 -
MPA構成:
認証のみで十分なため設計はシンプルだが、
フロントとバックエンドが密結合となり、拡張性・運用性では制約が生じやすい8
本記事が、同様の疑問を持つ方々にとって、整理や理解の一助となれば幸いである。
-
AWS Cognitoが提供する機能に対応するAzureのサービスはAzure Microsoft IDとなる。 https://learn.microsoft.com/ja-jp/azure/architecture/aws-professional/security-identity ↩
-
Azure Microsoft ID プラットフォームはMicrosoft Entra ID(旧称:Azure AD)を内包しており、Cognitoに対応する機能としてはMicrosoft Entra IDが示されることも多い。 https://learn.microsoft.com/ja-jp/entra/identity-platform/v2-overview ↩
-
https://openid-foundation-japan.github.io/openid-connect-core-1_0.ja.html ↩
-
ちょっと雑過ぎる言い方かもしれない。正確には先ず前提として「
The resource owner password credentials grant MUST NOT be used.
」(Resource Owner Password Credentials = ROPCとも呼ばれ、Authentication Server = AS以外の場所、たとえばClientでのID/パスワード入力を受け付けて、それをASへ送る形式は避けろ」というのがある。理由は「入力元のクライアントのなりすましを防げない」から。もちろんクライアントを十分に信頼できる場合は除く(The credentials should only be used when there is a high degree of trust between the resource owner and the client
, https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-30#section-1.3.3 )。で、ASで認証して(ID/パスワードでもいいし、それ以外でもよい)その結果をクライアントへ認可コードとして送るわけだが、この認可コードを受け取るクライアントの「なりすまし」も防ぐ必要がある。OAuth2.0はどちらと言うと後者がメイン。 https://datatracker.ietf.org/doc/id/draft-ietf-oauth-security-topics-16.html#section-2.4 ↩ -
OAuth2.0はもともと「サードパーティにアクセストークンを与える場合には」で考えられているので、ファーストパーティーで構成する場合はそこまで考える必要が無いことも多い(最適な回答にならないことも多い)、という意図。
However, the use of OAuth for governing access between the frontend and the backend is often not needed.
https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-7.1 ↩ -
本記事は、「可用性・スケーラビリティ・疎結合性に優れるアーキテクチャを採用」することを前提に書いたので、この詳細は割愛する。簡単に書くなら、可用性とスケーラビリティは「①フロントエンドとバックエンドを独立してデプロイ/スケールできるため、トラフィック急増時に特定コンポーネントだけ拡張可能、②障害発生時も他コンポーネントへの影響を局所化でき、システム全体の稼働率向上につながる」こと、疎結合性は「①APIを契約としてチーム横断で開発・運用でき、リリースサイクルを分離可能、②技術スタックの選択自由度が高く、マイクロフロントエンド/マイクロサービスとの親和性も良い」などが具体的な内容となる。 ↩
-
MPAについても、参考としての比較に出しただけなので、詳細は割愛する。簡単に書くなら「①小さな変更でも全体リリースが必要になり、変更時の影響範囲が大きくなりやすい、②負荷分散は全体にしかかけられず、部分的なスケールが困難」などが具体的な内容となる。 ↩