Cognito UserPool 実践入門 では書けなかった闇をブログに記す #技術書典

#技術書典 で "Cognito UserPool実践入門" という本を販売します。

BOOTHでも取り扱っています。 hiroga.booth.pm

これ、元々プロジェクトで経験したUserPool移行の苦労話を書こうと思って始めたのですが、いつの間にか入門書を書いていました。
なので苦労話の方はお蔵入りにしたのですが、もったいないのでここに記します。

Cognito UserPool 実践ユーザー移行 UserPool to UserPool with Federated Identity編

悲劇の始まり

Cognito初心者が絶対つまづく(と私が思っている)設定として、エイリアス属性(IDの代わりにログインに利用できる属性)があとから変更できないことがあります。

f:id:hiroga_cc:20190921161501p:plain
デフォルトで設定されるエイリアス属性

デフォルト設定だとメールアドレス・電話番号でのログインは不可能ですが、この設定は後から変更不可能です

私が関わっているプロジェクトもUserPoolを利用していたのですが、エイリアス属性に設定されていたのはメールアドレスだけでした。
そこにきて、「電話番号でログインできるようにしたい」という要件が登場したわけです。

初めはちゃちゃっと対応できると思ってましたが...Federated Identityを併用していたことで話が大変になります。

堅牢なFederated Identity

正直言ってUserPoolからUserPoolにデータを移行するだけなら話は簡単でした。
恐ろしいのは Federated Identityとの連携を維持しなければいけないことです。

f:id:hiroga_cc:20190921162412p:plain
⑤アプリケーションが利用するユーザーの一意識別子はFederated IdentityのIdentity ID

UserPoolとFederated Identityを連携している場合、バックエンドのアプリケーションで利用するユーザー識別子には Federated IdentityのIdentity IDを利用しなくてはいけません(じゃないと、OpenID Provider → Federaed Identityでも利用している場合に困りますよね?)

ところが!Federated IdentityのIdentity IDに対して、管理者権限で任意のログイン情報を紐付けることは不可能なのです
(それ自体はセキュリティの観点からあるべき姿だと思いますが...。)

f:id:hiroga_cc:20190921163333p:plain
古いUserPoolのユーザープロファイルとFederated IdentityのIdentity IDが紐付いている。管理者が勝手に新しいUserPoolのIdentity IDを連携することはできない。

これを解決するための、たった一つだけ許されたやり方...それは、管理者がユーザーの代わりにログインすることです。
それならユーザー移行Lambdaトリガーで余裕じゃん!と思われるかもしれませんが、余裕ではありませんでした

たったひとつの冴えないやり方

結論から言えば、ユーザー移行Lambdaトリガーを以下のように実装すれば対応可能です。

f:id:hiroga_cc:20190921170014p:plain
UserPool to UserPool with Federated Identity のユーザー移行

  1. クライアントは新UserPoolにログインを試行
  2. 新UserPoolはユーザーが見つからないのでユーザー移行Lambdaトリガーを起動
  3. Lambdaはユーザーの代わりに旧UserPoolにログイン。この際にIdToken(旧)を受け取る。
  4. Lambdaは新UserPoolに対して AdminCreateUser アクションを実行し、ユーザー移行Lambdaトリガー内でユーザーを作成 ※1
  5. Lambdaは新UserPoolに作成したユーザーとしてログイン。IdToken(新)を受け取る。
  6. 新旧のIdTokenを使って、 Federated IdentityにgetOpenIdToken アクションを実行。結果、旧のユーザーで取得したIdentity IDが新のユーザーに紐付く
  7. LambdaはエラーをThrow(自爆)(そうしないと、UserPoolが後続処理でユーザーを作成しようとし、そのユーザーはStep2で作成済みでエラーになるため)
  8. UserPoolはクライアントにエラーを返す ※2

※1 ユーザー移行Lambdaトリガーは、その返り値でUserPoolにユーザー作成のための情報を渡すLambda。これはバッドハックです。
※2 クライアント側もエラーを握りつぶすように修正しました。

なお、これに加えてパスワードリセット時は旧のUserPoolのパスワードをリセットするように設定する必要があります。

参考までに、通常のユーザー移行Lambdaトリガーのフローも載せておきます。

f:id:hiroga_cc:20190921171501p:plain
通常のユーザー移行Lambdaトリガーのフロー

シンプル!!!!!

しんどさ

検証のためにUserPoolになんどもユーザーを作成するのが一番大変でした。
その結果生まれたのが cognito-cli です。

github.com 注意: ちょっとバグってます。後で直す。

CLIでUserPoolの非管理者用APIを立たけるようになったことで生産性が爆上がりました。

それからデプロイもしんどかったです。Cognito UserPool 実践入門 でも書きましたが、ユーザー移行Lambdaトリガーは 2019-09-21時点でCloudFormation非対応なので...。
たぶん世の中的にCognitoをCloudFormationで管理している人が稀なのですが(だってそんなに更新しないでしょ?)

でもやってよかった

同じ問題にぶち当たる人は二度と現れてほしくないですが(だから執筆したんです!)、私個人はとても勉強になりました。

  • OpenID ConnectのIdTokenの中身を見たり...
  • Cognitoの仕様にちょっとだけ詳しくなったり...
  • 初の技術書典サークル参加につながったり...

人間必要になったときが一番勉強するんで、認証認可について学びを深められたのは良かったです。

最後に...

色々書きましたが、認証・認可をマネージドに済ませてアプリに集中するには、Cognitoはとてもいいサービスだと思っています。
もしCognito検討する人がいたら、公式ドキュメントに加えて私の本もチラッと見ていただければ幸いです!!!!!
hiroga.booth.pm