Cognitoユーザプールの外部IdP連携をちゃんと理解する

これまで認証といえばCognito一択みたいにアーキを描いて、あとはいい感じに連携してくれているみたいな理解で実際その裏で何がされているかを気にしてこなかった。AWS SAPの対策でやたらID連携が出てきたり、仕事上認証認可の細かいところを触れることがあり一度整理する。

# [そもそも] 認証認可の周辺知識整理

## ■ 認証と認可

認証 → 相手が誰かを同定する作業。ステータスコード401はリクエスタが誰だかわかってない状態。
認可 → 相手に権限を与える作業。ステータスコード403は誰だかわかった上で許可しない状態。

## ■ OAuth2.0

認可のためのプロトコル。
以下のメリットがある。

- クライアントアプリにリソースを提供したいがID/PWは渡したくない。
- PWのライフサイクルと分離したい。(ID/PWを渡すとPW変更のたびにアクセス権が失われるため)
- 部分的に権限を渡したい。(ID/PWだとアクセスする全ての操作ができることになってしまうため)

→ アクセストークンを用いてクライアントがリソースへアクセスできるようにしたもの。アクセストークンは予測不可能な文字列などが利用される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/2e5f38aa-f289-8ac1-4928-3980275d8e4e.png)

### 4種類のフロー

#### (1) Client Credentials Grant

結局、クライアントに埋め込まれた client ID / secret をアクセストークンに引き換えてるだけ。
永続的なクライアントのクレデンシャルを一時的なアクセストークンに変えることで一定の安全になるもののこのフローでできることはユーザに関連しない情報を取得することしかできない。(リソースオーナーを介さないので。)
用途としては公式アプリクライアントがログイン状態不問で実施する処理を行う場合など。(未ログイン状態で表示する情報をリソースサーバから取得する際にクライアントをチェックする目的。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/c2efea53-d036-7c83-021d-cb2e95cbf440.png)

#### (2) Resource Owner Password Credentials Grant

結局、ユーザ ID / PW をアクセストークンに引き換えてるだけ。
ClientがID/PWを知ってしまうので悪意のあるClientだとなりすまされる可能性がある。
公式クライアントなどでしか利用できない方法。
もはや使ってはいけないとまで言われてる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/228dfd9b-bc21-59f0-b14e-27e09ceedd19.png)

#### (3) Implicit Grant

モバイルやブラウザ上のJSアプリなどエンドユーザ支配下にあるノードの上で動くアプリケーション向け。パブリッククライアントと呼ばれる。クライアントシークレットを保護できないため割り当てない。
このフローはクライアントにID/PWを渡さないがユーザエージェントにはアクセストークンが丸見え。
これも今では非推奨らしい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/9f3d04b3-3e92-b10a-8436-0c5306226ca7.png)

#### (4) Authorization Code Grant

OAuthの理想が詰まったフロー。clientにだけアクセストークンを渡すことができる。
サーバサイドWebアプリClientなど安全にクライアントシークレットを保存できるノードで動くアプリケーション向け。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/f0fdd30b-6e08-7e3d-a33c-c0624e3bdc20.png)

なお、このフローは認可横取り攻撃に脆弱である。悪意のあるClientが認可コードを正しいClientへリダイレクトせず認可コード、アクセストークンが横取りされうる。
これに対しPKCE (Proof Key for Code Exchange by OAuth Public Client)という対策が作られた。これは検証用文字列(コードチャレンジ)を生成し、そのハッシュ化法を予めサーバに通知しておくことで認可リクエストと認可コードの送り主が一緒かをサーバ側で検証できるようにする手法である。

#### (appendix) リフレッシュトークンフロー

ここはOAuthで標準化された範囲外。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/7de949a9-13af-391c-0b19-accec6a32431.png)

## まとめ

基本型のAuthorization Code Grantに照らしてOAuthの全体図を書くと以下。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/877d4258-bef0-96ca-a16c-f79764e02cde.png)

## ■ OpenID Connect
OAuthが一般的になるにつれてOAuthを認証に使われるようになった。
これはとても危険。当然ながらトークンを持っている＝本人ではないから。

→ 認証部分をカバーした拡張仕様として`OpenID Connect`が生まれた。
OpenID ConnectではOAuth2.0に以下の点を加えている。

- IDプロバイダ(IdP, OP)にて認可だけではなく認証を実施
- アクセストークンとともにIDトークンを返却する。
- クライアント側ではIDトークンをIdPの公開鍵で検証する。

以下のバリエーションがある。

1. Basic Client Profile (OAuth2.0のAuthorization Code Grantの拡張)
2. Implicit Client Profile (OAuth2.0のImplicit Grantの拡張)

ここでは前者について考える。

### Basic Client Profile 
OAuth2.0(Authorization Code Grant)に重ねると以下の感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/da9a6d6b-e0cf-4176-181a-e9372ec6ea1f.png)

# [本題] Cognitoって何してるの？

結局ブラックベルトが一番理解を進めてくれた気がする。
https://aws.amazon.com/jp/blogs/news/webinar-bb-amazon-cognito-2020/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/56c26895-ef4c-f7fd-b441-107207e6a196.png)
(▲ BlackBeltより転載)

Cognitoユーザプールの使い方には大きく2種類ある。
1. 認証UIを自前実装し`Cognito Identity Provider API`を利用して認証・各種トークンのやりとりを行う。 (※用語が古いのか`Cognito IdP API`のリファレンスがヒットせず、おそらく今では`CognitoユーザプールAPI`のことと解釈した。)
2. Cognitoが提供する`Hosted UI`をフロントとし`Cognito Auth API`を利用して認証・各種トークンのやりとりを行う。(※用語が古いのか`Cognito Auth API`のリファレンスがヒットせず、おそらく今では`Cognitoフェデレーションエンドポイント`のことと解釈した。*1)

*1 : 緒言としては弱いが、2,3年前の以下の記事の`参考`にてCognito Auth APIリファレンスへのリンクが記載されており、開くといずれもCognitoフェデレーションエンドポイントとHosted UIについて触れたページに飛んだため一旦その理解で進めた。

- https://blog.linkode.co.jp/entry/2020/03/17/143612
- https://qiita.com/KWS_0901/items/3ec395d47424533abcfb
- 
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-userpools-server-contract-reference.html

当初この2パターンという分け方の認識がずれていて理解に時間がかかった。
ここがわかるとあとはシーケンスで理解ができる。

## ① `Hosted UI`を使うケース

このケースがよく使われるのはそもそもWebコンテンツをダウンロードさせる前に認証をかけ、Lambda@Edgeで弾く場合に使われている印象。(偏見かも)
ここではLambda@EdgeでCognitoとやりとりして認証する場合で書く。
RPってどこに該当するんだと悩んだが結局IdPとやりとりして認証を行うのはLambda@EdgeになることからおそらくCF+L@EがRPと考えた。

### ①-A `Hosted UI` + CognitoをIdPとするケース

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/00bb393d-4976-29d4-a2b2-47c975b1911d.png)

### ①-B `Hosted UI` + CognitoをOIDCに準拠した外部IdPと連携するケース

なお`identity_provider=xxxxx`と外部IdPをクエリパラメータで明示的に指定しておくことで`Hosted UI`をスキップ可能。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/d597bf30-d9c1-72ea-4b1a-a6680592705b.png)

## ② `Hosted UI`を使わずCognito APIをコールするケース

SPAを利用する場合によく使われてるイメージ。
最初にSPAのコンテンツを取得するの自体はできるが、認証を必要とするリソースへのアクセスの際には認証画面を表示する。この時に`Hosted UI`が出てくる構成を使ってるのはあまり見たことがない。(偏見かも)
amplify UIのAuthenticatorクラスみたいなやつを使って表示させていたような気がするのでそこに合わせて書く。
同じくどこがRPなのか問題だが、①と違いこっちはL@Eがなく認証APIの処理はブラウザ上で動くSPAになるのでこいつがRPになると考えた。

### ②-A 自前UI + CognitoをIdPとするケース

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/8b1de7ab-f8b9-3b81-0e48-71060aeeaa3b.png)

### ②-B 自前UI + Cognitoを外部IdPと連携するケース

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/7c7480ab-268d-e400-8f75-a68eeb2e2e0e.png)

## SAML連携

Cognitoコンソールで連携するIdPを追加する画面を確認すると、SAMLも選択可能であることが見える。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/afdf8e0d-5a50-95d2-eb42-78ae0628df75.png)

認証のプロトコルであるSAMLのIdPを選択した場合、認可に係るトークン発行などの部分はCognitoがカバーしてくれるということと予想。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/caa3cae1-43a8-0dd4-4589-3fd6dc4767e4.png)
▲ [公式ドキュメント](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-saml-idp-authentication.html)より転載。

OIDCでは`/oauth2/idpresponse`にリダイレクトしたが、ここではSAMLアサーションを受け取って`/saml2/idpresponse`にリダイレクトすることでトークンが返るのだと思う。

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/federation-endpoints.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/c78dba48-2109-4cd7-518a-f3b7d7b07a18.png)


## 参考
- https://dev.classmethod.jp/articles/developers-io-2017-session-report-oauth2/
- https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be
- https://logmi.jp/tech/articles/322829
- https://qiita.com/TakahikoKawasaki/items/200951e5b5929f840a1f
- https://speakerdeck.com/d_endo/30fen-deopenid-connectwan-quan-nili-jie-sitatoyan-eruyouninarumian-qiang-hui
- https://aws.amazon.com/jp/blogs/networking-and-content-delivery/authorizationedge-using-cookies-protect-your-amazon-cloudfront-content-from-being-downloaded-by-unauthenticated-users/
- https://docs.aws.amazon.com/ja_jp/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html#API_InitiateAuth_ResponseElements
- https://aws.amazon.com/jp/blogs/news/webinar-bb-amazon-cognito-2020/
- https://qiita.com/tmiki/items/f36edc6c7473f5baa398
- https://s1r-j.hatenablog.com/entry/2022/08/26/013139
- https://blog.linkode.co.jp/entry/2020/03/17/143612
- https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-userpools-server-contract-reference.html
- https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/login-endpoint.html
- https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-saml-idp-authentication.html
- https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/federation-endpoints.html
- https://github.com/awslabs/aws-jwt-verify/tree/main/src
- https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html#amazon-cognito-user-pools-using-tokens-aws-jwt-verify
- https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-oidc-flow.html
- https://www.authlete.com/ja/developers/tutorial/cognito/
- https://qiita.com/poruruba/items/a284b9f522d8d6e1e3f9
- https://sogo.dev/posts/2022/12/openid-connect-fastapi
- https://developer.mamezou-tech.com/blogs/2023/01/23/amazon-cognito/
- https://qiita.com/TakahikoKawasaki/items/701e093b527d826fd62c

# 蛇足

## 閉域網でのCognito利用

シーケンスに出てきたCognitoの各種エンドポイントはいずれもパブリックIPアドレス(実際にはDNS名)で公開されており、閉域網で利用できない。
以下にプライベートサブネットからどうアクセスするかのバリエーションが示されているのをみつけたがいずれもインターネットに出る必要がある。

https://aws.amazon.com/jp/builders-flash/202210/cognito-userpool-endpoint/?awsf.filter-name=*all