AWS Cognitoと学ぶOAuthとOpenID Connect
はじめに
何の記事か
- Cognitoで外部プロバイダー(GitHub)認証を実装しようとして断念した体験談
- 試行錯誤して学んだことのまとめ(OAuth2.0とOIDCの大まかなフローとCognitoの機能について)
実装しようと頑張ったけどできなかった!でも学ぶこともあったよ!という感じの記事です。
この記事で話さないこと
- 認可・認証についての基礎
- Cognitoの基礎、概要
やろうとしたこと、やったこと
フロントはNext.js、認証ライブラリにNextAuthを利用。
ユーザーはGitHubアカウントでログイン、主なコンテンツはGitHubと連携するアプリケーションです。
認証機能の実装にAWS Cognitoを利用。
外部IDプロバイダーとしてGitHubを設定。(OAuthAppアプリを使用)
Cognitoがサポートしているのは以下の三つです。
- ソーシャルIDプロバイダー(Facebook, Google, Amazon, Apple)
- SAMLプロバイダー
- OpenID Connect(OIDC)プロバイダー
GitHubOAuthアプリはいずれにも対応していないので、OIDCプロバイダーとして使えるように何らかの方法でOIDCの仕組みを実装する必要があります。
今回はこちらを使用。
GitHub OAuthAppをOIDCプロバイダーとして使えるようにLambda関数とAPIGatewayでラップしたものです。
やろうとした構成はこんな感じです。
最終ゴールと結果は以下の通り。
- 【できた👏】GitHub認証でCognitoユーザープールにユーザーが追加される
- 【できた👏】ログイン中のGitHubユーザー名、メールアドレスをフロントで表示できる
- 【できなかった😰】GitHubアクセストークンをフロントで保持
(できるのはCognitoが発行するトークンのみ)
GitHubが発行するアクセストークンはCognitoで止まり、クライアントには返ってきません。なんとかして取得できないかと上記のLambda関数をカスタムして使おうとしましたが、断念。
Cognitoを用いるのにCognitoが発行するトークンを使わずに、GitHubが発行するアクセストークンでクライアントからGitHubAPIに直接リクエストを送る(Cognitoを通さずに!)こと自体が間違っているというか、ユースケースとしてほぼあり得ない、想定されていないのかなと思います。
Cognitoが主に解決するのはアプリケーション独自または外部IdPを用いた認証により発行したトークンを用いてAWSリソースへのアクセスを可能にすることなので、今回はアクセス先がAWSリソースではなく外部(GitHub)であるためトークン周りがややこしくなっていました。
またGitHubAPIの利用がメインとなりGitHub以外のアカウント連携を想定していないことから、この複雑さを乗り越えるほどCognitoを用いる強いメリットはないと考えました。
もしCognitoを使うならGitHubにより認証されたユーザーがIDプール経由で他のAWSサービス(APIサーバー、リソースサーバーにあたるもの)にアクセスできるようにする構成かなと思います。
OAuthとOIDC
OAuth2.0は認可のためのプロトコルです。
RFC6749
OpenID Connect(OIDC)はOAuth2.0を拡張して認証もできるようにしたもの。
OpenIDファウンデーションジャパン
OAuth2.0
RFC6749でOAuth2.0のいくつかの認可フロー、エンドポイントが定義されています。
【認可フロー】
- 認可コードフロー
- インプリシットフロー
- リソースオーナー・パスワード・クレデンシャルズフロー
- クライアント・クレデンシャルズフロー
- リフレッシュトークンフロー
【エンドポイント】※認可サーバーのエンドポイント
- 認可エンドポイント
- トークンエンドポイント
厳密には、エンドポイントは他にもあります。
フローによって各エンドポイントを使ったり使わなかったり。
今回は認可コードフローのみ簡単に図解してみます。
OAuth2.0 認可コードフローの流れ
少し分かりづらいですが、③と④の過程では認可サーバーとユーザーの間でのみやり取りが行われてアプリケーションを介しません。
こちらの記事がとても参考になりました。全フローが細かく解説されています。
OpenID Connect(OIDC)
OIDCは認可プロトコルであるOAuth2.0を「認証」のために拡張した仕様です。
上記図の認可サーバーはIDプロバイダーと呼ばれ、認証サーバーに当たります。
大きく違うのは、二点です。
- 上記図④でユーザーの本人確認(認証)を必要とする(ID/PW、OTP…など)
- 上記図⑧でアクセストークンを発行する際に「IDトークン」も発行、さらにアプリケーションでIDトークンの検証が行われます。また、検証に使用する公開鍵を認証サーバーのJWKSエンドポイント(JWKS=Json Web Key Set)から取得します。
アクセストークンはリソースへのアクセスを認可したという証明書のようなもので、これさえあればリソースへのアクセスが可能です。
IDトークンにはユーザーの認証情報(OIDCの仕様として定義あり)がクレームとして含まれています。そのためIDトークンの検証を行うことで、アプリケーションは認証結果を保持することができます。この認証結果を用いてリソースへのアクセスを行うのがOIDCです。
かなり簡略化して図解したものがこちらです。
OIDCの流れ
OAuthは認可、OIDCは認証のプロトコルだという違いを言葉では理解していましたが、具体的に何が「認証」としているのか疑問でした。OIDCではIDトークンを発行し、これを検証するというフェーズが認可プロトコルOAuthとの大きな違いでありOIDCを「認証」たらしめている部分です。
Cognitoって何してるの?
外部IDプロバイダーとの連携
「外部IDプロバイダー経由でのフェデレーションサインイン」を提供しています。
Cognitoを外部IDプロバイダーと接続することで、アプリケーションと一つ以上の外部IDプロバイダーのブリッジとして機能します。
コンソール画面のユーザープール>サインインエクスペリエンス>フェデレーテッドアイデンティティプロバイダーのサインインからアイデンティティプロバイダーを追加することができます。(Facebook/Google/Amazon/Apple/SAML/OIDCのいずれか)
APIエンドポイントの提供
Cognito自身もまたOIDCの仕様に準拠したAPIエンドポイントを提供しています。
ユーザープールにドメインを割り当てることで各エンドポイントが有効になります。
これらのエンドポイントはアプリケーションにとってOAuth2.0、OIDC、SAMLの各フローにおけるエンドポイントであり、外部IDプロバイダーにとってもまたリダイレクト先のエンドポイントとなります。
これらのエンドポイントを利用することで、Cognitoがアプリケーションと外部IDプロバイダーとの橋渡しの役割を担っていると言えるのだと思います。
もちろんCognitoの機能はこれだけではないですし、またブリッジとしてのシンプルな役割だけでなく様々なフローに拡張して利用できるためユースケースに応じてアーキテクチャを検討していく必要があります。
つまづきポイント
どこに何を設定するか
実装したいアーキテクチャ通りに設定できていなかった。
構成とエンドポイントについて理解していなかったため、NextAuth、GitHub(OAuthアプリ)の設定を間違えていてうまくいきませんでした。
Cognitoの役割と動き、全体の構成をしっかり理解していればシンプルなことですが、ハマりました。当たり前ですが一つでも誤りがあると動かないですね。
またGitHubとCognitoで設定するコールバックURLも公式に記載しているエンドポイントを正しく指定できていなかったのでハマりポイントでした😰
余計なコードを書かない
NextAuthの設定ファイルに余計にオプションを書いてしまっていて、クライアント側の挙動がおかしくなっていました。まずは最低限、オプションは必要に応じて、ですね。コード量は少ない方がいい。
とはいえおかげでその他オプションの挙動について勉強にはなりました。
反省
- 答えは公式ドキュメントに書いてある。
- やりたいことの構成を理解する。まずはゼロから、最低限のステップを踏んでいく。
できない!にハマると公式ドキュメントを読んでいるのに何故か余計なコードや設定を足してしまう。引きずられる前に、さっと気づいて、一旦リセットして0から組み直すスピード感をつけたいです。 認証まわりは本当にややこしくて、ハマりやすい。 結果としてできませんでしたが、頑張ってみようとOIDCとして使えるようラップしたソースコードを読み解く過程でOIDCについての理解が深まりました。Cognitoの仕様についても学べたのでよかったです。
間違いなどありましたらご指摘いただけるとうれしいです!
Discussion