最適な認証基盤を構築する (Amazon Cognitoサンプル編)


仕事で認証基盤を刷新しようとしており、以下のようにIDaaS周りを色々と調査していました。

https://tech-blog.tsukaby.com/archives/1548

https://tech-blog.tsukaby.com/archives/1551

ここまでの結論としてはIDaaSは色々とあるもののAuth0は高くて使えないので、FirebaseかCognitoがバランスが良く、要件的にはCognitoが合っていそう、という感じです。

今回の記事では実際のコード例を調査して、実践的な認証のサンプルコードを用意します。

必要な機能

私が最近仕事で使うことが多い構成で作ることとします。リソースサーバー(APIサーバー)はRails、フロントエンドはNext.jsとします。前回の記事で軽く触れていますが、以下が必要な機能やタスクです。

  1. AWS上でCognitoユーザープールを作成する

  2. RailsとNext.jsの初期コードを用意する (rails newとyarn create next-app)

  3. AmplifyのJSライブラリを利用しつつ、Next.jsでログイン画面(Home画面)、ログイン後画面(Dashbaord画面)、パスワードリセット画面を作成する (自己サインアップは禁止するため、サインアップ画面は無し。ログイン後画面はログインしていない場合、Home画面へリダイレクトする)

  4. Cognitoのimport機能を利用し、適当なユーザーをユーザープールに取り込む。 (実際は利用ユースケースによって不要だったり、サインアップで事足りるが、私の仕事上ではこれが必要なため)

  5. リソースサーバーに適当なAPIを追加し、正規のaccessTokenがなければ利用できないようにする。このAPIをログイン後画面で利用し、取得した値を画面上に表示する。

  6. ログイン後画面を拡張し、MFAを設定可能にする。また、ログイン画面を変更し、MFA有効時は追加の認証を求めるようにする。

私の場合、仕事でSAML連携やソーシャルログインなどを利用する可能性もありますが、今回は一旦ここまでとします。

サンプルコード

この方のサンプルはGitHubに完全なものがあるし、私とやりたいことが近いのでとても参考になりました。

Next.js + CognitoでHosted UIを使わずに自前のUIからサインイン、サインアップ、AuthGuardなどの認証周りの挙動を実現するサンプルアプリを公開しました

このサンプルの場合は自己サインアップが可能であり、MFAのセットアップ画面や追加認証画面が存在しないのでMFAは使えません。

このように要件に合わない部分があるため、それらを作り込みます。作ったコードは以下に置いてあります。

https://github.com/tsukaby/cognito-example

以下、一部解説します。

通常のサインアップとMFAのサインアップ

https://github.com/tsukaby/cognito-example/blob/0d573bbbe12e6241c7e7d9203e8204b9e3088b45/frontend/pages/index.tsx#L105

通常のSignIn(ログイン)はこちらのようにユーザーID(今回はメアド)とパスワードを受け付けて、Auth.signInを呼び出すだけです。

このときに初期セットアップ時は新しいパスワードの設定を求める必要があります。また、MFAが有効化されていれば追加の認証を求める必要があります。これらのケースでそれぞれ分岐します。

MFAの場合は追加のコードを入力させ、

https://github.com/tsukaby/cognito-example/blob/0d573bbbe12e6241c7e7d9203e8204b9e3088b45/frontend/pages/index.tsx#L166

こちらのようにAuth.confirmSignInを呼び出します。このPromiseが成功で解決出来た場合はログイン成功とみなせるので、ログイン後の画面に遷移させます。

MFAの設定

https://github.com/tsukaby/cognito-example/blob/0d573bbbe12e6241c7e7d9203e8204b9e3088b45/frontend/pages/private/setup_totp.tsx#L40

Auth.setupTOTPによってセットアップのための情報が取得できるため、これとsub(Cognito user pool上のユーザID)を使ってQRCode用の文字列を作ります。QRCodeを読み込ませて検証用のコードをユーザに取得させ、それを入力させて検証します。検証および追加の設定をもって、MFA設定完了とします。

https://github.com/tsukaby/cognito-example/blob/0d573bbbe12e6241c7e7d9203e8204b9e3088b45/frontend/pages/private/setup_totp.tsx#L57-L59

具体的にはこのあたりのAuth.verifyTotpTokenによって検証し、成功ならばAuth.setPreferredMFAによって設定を行います。

その他

今回のコードではGoogle AuthenticatorなどによるMFAを前提としましたが、場合によってはSMSによるMFAが必要な場合もあると思います。そのようなケースでもほぼ似たようなコードで対応できると思いますが、Cognito側でSMSを有効にする必要がありますし、そのあたりは少し注意が必要かもしれません。

私はCognito側の各種送信メッセージを全くカスタマイズしませんでしたが、実際のプロダクション利用時は適切にカスタマイズしたほうが良さそうです。例えばパスワードリセットのリクエストを行うとCognitoからメールが届いて、そこにcodeが載っていますが、URLは載っていません。プロダクション利用時は自社サービスのURLなどを載せると良さそうです。