Amazon CognitoとCloudFrontで特定のユーザのみが閲覧できる仕組みを作る

こんにちは、久保田(@kubotak_public)です

今回の記事はAmazon CognitoとCloudFrontを利用して特定のユーザのみが閲覧できる仕組みを作る(表題どおり)となります。
弊社での利用シーンとしてSchemaSpyで生成したER図(というよりドキュメント)を特定のユーザ、つまり弊社の人間のみが閲覧できる仕組みを作りたいなという動機で作成しました。
例えばフロントエンドのStorybookなども社内展開する際にはS3に置いたものをどうにかアクセス制限して提供したい・・・みたいなニーズってあると思うのですが、まさにそういう場合にうってつけではないかと思います。

Amazon Cognito

そもそもCognitoとは?という方向けに説明しますと、Auth0Firebase Authenticationに代表されるIDaaSと呼ばれるたぐいのサービスです。
認証の仕組みやユーザーの管理などを内包するサービスです。

全体像

まずは全体像を共有します。
CloudFrontを経由してS3の静的データを配信する素朴な構成の中に、CloudFrontにLambda@Edgeを紐付けて前段に認証の仕組みを挟んでおります。
また、CognitoにはGoogle認証を紐付けてGoogleアカウントによるログインができるようにしています。

① CloudFront

もう少し詳細に説明していきたいと思います。
まずはCloudFrontがS3を参照して静的データを返すという素朴な構成です。
しかし、CloudFrontのビヘイビアで関数の関連付けを行い、ビューワーリクエスでLambda関数が起動するように設定します。
このようにCloudFrontでLambda関数を紐付けるものがLambda@Edgedです。

② Lambda@Edge

CloudFrontで紐付けているLambda関数はLambda@Edgeとして利用するので以下の成約があることに注意が必要です。

  • Lambda@EdgeはバージニアリージョンのLambda関数しか設定できない
  • Lambda@Edgeは環境変数が使えない
  • Lambda@Edgeは5s以内にレスポンスを返さなければならない

さて、このLambda@EdgeではCognitoに連携して以下の処理を行います。

  1. 認証済みかどうかチェック
  2. 未認証の場合はGoogleログインを促す
  3. 認証済みの場合はリクエストを許可する

これを実装しましょう・・・といっても全然お手軽感ないですよね。安心してください。便利なライブラリがあります。

github.com

awslabsが提供してるLambda@Edge用のCognito認証ライブラリを使うと簡単に上記仕組みが実装できます。

※ライブラリのREADMEの通り

const { Authenticator } = require('cognito-at-edge');

const authenticator = new Authenticator({
  // Replace these parameter values with those of your own environment
  region: 'us-east-1', // user pool region
  userPoolId: 'us-east-1_tyo1a1FHH', // user pool ID
  userPoolAppId: '63gcbm2jmskokurt5ku9fhejc6', // user pool app client ID
  userPoolDomain: 'domain.auth.us-east-1.amazoncognito.com', // user pool domain
});

exports.handler = async (request) => authenticator.handle(request);

先述の通り、Lambda@Edgeでは環境変数が使えないのでここではベタ書きする必要があります。

③ Cognito

続いてはCognitoでGoogle認証を追加します。 また、Google認証時に特定のドメインのみをサインアップ対象とするために一工夫します。

CognitoでGoogle認証を追加する方法はクラスメソッドさんの記事を参照ください。

dev.classmethod.jp

サインアップ時に特定の処理を挟む場合はLambda関数を指定することができます。
後述でLambda関数については紹介するのでここでは登録の方法のみ紹介です。 CognitoではこのようにイベントにフックするようにLambda関数を登録することができます。

GCP

Google認証を行うのでGCPの設定も必要ですが、これに関しては先述のクラスメソッドさんの記事に紹介がありますので割愛します。

⑤ Lambda

Cognitoのサインアップ時に登録するLambda関数を作成します。
こちらはLambda@Edgeではないので東京リージョンで作成しても問題ありません。

この関数はPythonで作成しています。

def lambda_handler(event, context):
    print(event)
    email = event["request"]["userAttributes"]["email"]
    print(email)
    domain = email.split('@')[1]
    if "example.com" == domain:
        print("OK!")
        return event
    raise Exception("bye-bye!")

とてもシンプルな実装です。
Google認証後に受け取ったメールアドレスのドメイン部分を抜き出して特定のドメインであるかどうかをチェックします。
一致した場合はそのまま受け取ったイベントを返し、そうでない場合は例外となります。
この例ではexamile.comドメインのメールユーザーのみサインアップが許可されることになります。

さいごに

いかがでしょうか?
実装自体はほぼやってないと言っても過言ではないレベルでAWSのサービスを組み合わせただけで認証機能を実現できました。
最初はER図用に作った仕組みではありましたが、開発環境のアクセス制御に関しても現在ではこの仕組で運用しています。
BASIC認証等ではなく、Cognitoによるユーザー認証になるので退職者が出た場合にも安全に運用を続けることができますね。

今回の記事はここまで!