Cognito の外部 IdP として Cognito を使う 

# はじめに
今回は AWS が認証機能として提供している Cognito ユーザープールに関する記事となります。
Cognito ユーザープールとは何ぞやという方は[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-identity-pools.html)をお読みください。

Cognito では外部の IdP を使用して外部 IdP 経由のサインインを行うことができます。
使用できる外部 IdP のプロトコルとしては OpenID Connect (OIDC) と SAML になります。
ここで、あることに気が付きます。OIDC が使えるのであれば、Cognito ユーザープール自体を外部 IdP とした構成ができるのではないか。。！

というわけで試してみました。
Cognito ユーザープールで外部 IdP を使ったフェデレーションサインインを試してみたいけど、使えそうな IdP なんて持ってない、という方には参考になるか思います。

実装方法を読む前に、Cognito ユーザープールにおける[外部 OIDC IdP を用いた際の認証フロー](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-oidc-flow.html)について確認しておくと理解が深まるかと思います。

# 実装
既に Cognito ユーザープールが2つ用意されていることを前提に始めます。
また、外部 IdP として使用するユーザープールを IdP_UserPool、Relying Party として使用するユーザープールを RP_UserPool と呼びます。

## 1. IdP_UserPool でアプリクライアントを作成
IdP_UserPool で新しくアプリクライアントを作成します。大事なポイントは２つです。
1. クライアントシークレットを発行する
RP_UserPool で外部 IdP を設定する際に、クライアントシークレットが必要となります。
クライアントシークレットはアプリクライアントの作成後に
発行することはできないため、忘れずに発行しておきましょう。
![スクリーンショット (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115963/c8412f01-87c2-aed0-e296-016878bbab1d.png)

2. コールバック URL に RP_UserPool のエンドポイントを設定する
コールバック URL に RP_UserPool のエンドポイントである https://ユーザープールドメイン/oauth2/idpresponse を設定します。
ユーザープールドメインは RP_UserPool で設定しているドメインと置き換えます。
このエンドポイントは、外部 IdP にて認証を完了させた後に、Authorization Code を伴って RP_UserPool に戻る際のリダイレクト先となります。
Cognito がフェデレーションサインインで使用するエンドポイントの一覧は[こちら](https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/federation-endpoints.html)に記載があります。
![スクリーンショット (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115963/5a4d94a6-d04e-0f45-27ef-5df395c270b1.png)

その他の項目はお好きな設定にしてください。
ただし、OAuth 2.0 許可タイプには「認証コード付与」、OpenID Connect のスコープには OpenID を含めるようにしてください。

## 2. RP_UserPool でアイデンティティプロバイダーを作成
RP_UserPool で新しくアイデンティティプロバイダーを作成します。

フェデレーティッドサインインのオプションでは OpenID Connect を選択します。
![スクリーンショット (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115963/a7d96204-7245-38e7-2afb-567891c04820.png)

詳細の設定画面が表示されるので、以下のように設定します。

プロバイダー名：任意の名前（ここでは IdP-UserPool にします）
クライアント ID：IdP_UserPool のクライアント ID
クライアントシークレット：IdP_UserPool のクライアントシークレット
許可されたスコープ：少なくとも openid を設定します。その他のスコープが必要な場合は追記します。
属性のリクエストメソッド：GET
OIDC エンドポイントを取得：
　- セットアップ方法：発行者 URL を通じた自動入力
　- 発行者 URL：https://cognito-idp.ap-northeast-1.amazonaws.com/IdP_UserPool_ID （IdP_UserPool_ID は IdP_UserPool のユーザープール ID と置き換えます）
![スクリーンショット (10).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115963/cd8fd0ec-9470-ca77-239c-c5ceb18a607c.png)
発行者 URL の情報をもとに、Cognito が Discovery Endpoint（/.well-known/openid-configuration）にアクセスして、必要なエンドポイント情報を自動で設定してくれます。
以下の URL にアクセスすることで各エンドポイントの情報が見れるのでよかったら確認してみてください。
https://cognito-idp.ap-northeast-1.amazonaws.com/IdP_UserPool_ID/.well-known/openid-configuration

## 3. RP_UserPool のアプリクライアントを設定
RP_UserPool のアプリクライアントで、作成した ID プロバイダーが使用できるようにします。
アプリクライアントのホストされた UI 設定から、ID プロバイダーに IdP-UserPool と Cognito ユーザープールを設定します。
新しくアプリクライアントを作成しても、既存のアプリクライアントの設定変更をしてもどちらでも構いません。
![スクリーンショット (12).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115963/93533e7a-8317-f89d-40b3-c3785903e994.png)

## 4. テストする
3 で設定したアプリクライアントからホストされた UI にアクセスします。
すると、IdP-UserPool のボタンが表示されます。
このボタンを押すと IdP_UserPool のホストされた UI に遷移し、サインインを行うと RP_UserPool の redirect_uri に認可コードを伴ってリダイレクトされます。
![スクリーンショット (13).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115963/be04d0a6-039c-bb47-4400-04f70d19ed0c.png)

3 の設定で ID プロバイダーとして IdP-UserPool のみを設定することも可能です。
その場合、アプリクライアントの「ホストされた UI を表示」からホストされた UI にアクセスすると、IdP_UserPool の認証画面に直接リダイレクトされる動作となります。

# 番外編
お互いのユーザープールをお互いの外部 IdP として設定すれば、サインイン時に外部 IdP を選択してお互いのサインイン画面を行き来し続けることで無限 Cognito が可能なのでは？と、ふと思い立って試してみました。

結果的に、サインインを行う ID プロバイダーを選択する部分では、無限にユーザープールを行き来することができました！
意気揚々とユーザープールを行き来し続け、自分がどっちのユーザープールのサインイン画面にいるかもわからなくなった頃に満を持してサインインしてみましたが、ユーザープールAでのサインインからユーザープールBにフェデレーションサインインが成功して、ユーザープールAに再度リダイレクトするぞというところで Invalid state エラーが発生してあえなく撃沈しました。State の値が保持し続けられないみたいですね。
Cognito 旅行は片道切符のようなので、お気を付けください。

# おわりに
今回は Cognito ユーザープールの外部 IdP に Cognito ユーザープールを設定してみました。
実際にこのような構成を取る場面はあまり思い浮かびませんが、既存のユーザープールのユーザーを流用して別のユーザープールで認証させたいといった場面で使えるかもしれません。（Migrate user Lambda trigger でユーザー移行してしまえばいい気もしますが）
Cognito のフェデレーションサインインを手軽に試してみる方法としてはよいかと思います。