Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
13
Help us understand the problem. What is going on with this article?
@netebakari

ALB + CognitoでGoogleアカウント認証をかける

この記事はクラスメソッドさんの記事『Amazon CognitoユーザープールLambdaトリガーでALB認証のメールアドレスを制限する | Developers.IO]』の手順をちょっと(うまくいかないところを試行錯誤しつつ)詳しく書いたものです。

何ができるか

認証機能など何もないWebアプリがEC2で動いているとします。このEC2を

  • ALBの下にぶら下げる
  • Cognitoの仕組みを使い、特定のGoogleアカウントでログインしているときだけアクセスできるようにする

という仕組みをサーバーレス(AWS ALB + Amazon Cognito + Google Cloud Platform + AWS Lambda)で構築します。

前置き:ALB + Cognitoの何が混乱を招くか

Amazon Cognitoは素晴らしいサービスなのですが、「ユーザープール」と「IDプール」があり、初歩的なこと1であればどちらを使っても似たようなことができるので話がとっちらかる傾向にあります。
また、さらにややこしいことにGoogleはOpenIDプロバイダでもあり、Cognitoを使わなくてもALBのOIDC認証でGoogleアカウント認証ができてしまったりします(G Suiteを利用している場合、組織内アカウントのみを認証対象とすることでCognitoを使わずに済ませることも可能です)。この記事では

  • ALBのOIDC認証機能は使わない
  • Cognitoのユーザープールを使う
  • CognitoのIDプールは使わない

ということを明確にしておきます。

やっていく

実際に手を動かして進めていきましょう。

Google Cloud Platformで新規プロジェクト作成

Google Cloud Platformのコンソールにログインし、適当な名前でプロジェクトを作成します。

スクリーンショット

image.png

OAuth同意画面作成

【ナビゲーションメニュー > APIとサービス > OAuth同意画面】と進み、OAuth同意画面を作成します。

image.png

G Suiteを利用している場合、ここで「内部」を選択することができます2。「内部」を選ぶと、組織外部のGoogleアカウントはこのプロジェクトで認証をパスすることはできなくなります。これはつまり何を間違えても外部の人が認証を通ってしまうことがあり得ないということを意味しますので、特に必要がなければ「内部」の方が安全です。
 

スクリーンショット

image.png

見ての通り、必要なスコープはデフォルトで付いてくるので特に追加する必要はありません。とりあえず名前だけ入力して保存します。

ここで一応:スコープってなんだっけ

OAuthを使ったシステムでは、ユーザーがアプリケーションへ直接パスワード等を送るのではなく、「アプリケーションがこんな権限(=スコープ)を要求してるけどどうする?」とGoogleのドメイン上で認証画面が表示されるというのがキモでした。スコープを増やせばGmailを送信したりGoogle Driveへアクセスしたりできるようになるわけですが、ここにある email profile openid は無条件でくっついてくる最低限のスコープだということになります。

OAuth 2.0クライアントID作成

続いて【認証画面】へ移動し、「認証情報を作成」→「OAuthクライアントID」と進んで「作成」をクリックします。

image.png

「アプリケーションの種類」は「ウェブアプリケーション」を選択して「作成」ボタンを押します。

スクリーンショット

image.png

image.png

OAuth 2.0クライアントIDが作成され、ポップアップでクライアントIDとクライアントシークレットが表示されます。この値をメモっておきます。

なお、この値はいつでも【認証情報 > OAuth 2.0クライアントID】を開けば参照できますからポップアップは閉じてしまっても大丈夫です。

スクリーンショット

image.png

ALBの準備(前半)

こちらについては軽く触れるだけにします。

  1. ACMで適当な証明書を用意しておく
  2. インターネット向けのALBを作る
  3. HTTPSでアクセスを受け付け、証明書を割り当てる
  4. ターゲットグループはとりあえず空で作る
  5. Route 53などでALBに hello-cognito.example.com というような名前を割り当てる
  6. https://ALBのFQDN でALBにアクセスができるようになればOK
  7. リスナーのルール編集画面を開いておく

image.png

この画面は後で触ります。

Cognitoの準備

ユーザープールを作成する

新たにユーザープールを作成します。適当に名前を決めて、「デフォルトを確認する」→「プールを作成する」でいいです。

スクリーンショット

image.png

アプリクライアント作成

続けてアプリクライアントを作成します。

  • トークンの有効期限を適当な値(デフォルト値に設定)
  • 不要な「SRP (セキュアリモートパスワード) プロトコルベースの認証を有効にする」のチェックを外す

これでアプリクライアントIDが作成されます。アプリクライアントIDは後でALBのリスナーに設定します。

スクリーンショット

image.png

ドメイン名決定

Amazon Cognitoドメインを決めます(この操作により新たに取得されます)。
ここで決めた https://YOUR-DOMAIN-NAME.auth.ap-northeast-1.amazoncognito.com は後で使うのでメモっておきます。

スクリーンショット

image.png

Googleとの紐付け

【フェデレーション > IDプロバイダー】を開き、「アプリID」と「アプリシークレット」に先ほどメモっておいた値を入力します。承認スコープは email profile openid として「Googleの更新」をクリックします。

スクリーンショット

image.png

属性マッピング変更

【フェデレーション > 属性マッピング】を開き、「Google」タブからemailのマッピングを行って「変更の保存」をクリックします。

スクリーンショット

image.png

アプリクライアント設定

アプリクライアントの設定からスクリーンショットの通りに各種設定を行います。
コールバックURLは https://ELBに割り当てたFQDN/oauth2/idpresponse になります。
(※このパスはALBが横取りして認証に使うため、EC2が /oauth2/idpresponse というリクエストを受け取ることはありません)

image.png

保存すると「ホストされたUI」リンクが有効になりますが、こちらをクリックする前にGoogle側の設定を済ませます。

Google側の設定

Googleの設定画面に戻り、

  • 承認済みのJavaScript生成元:
    • https://ELBに割り当てたFQDN
  • 承認済みのリダイレクトURI:
    • 「ドメイン名」で決めた値 + /oauth2/idpresponse

を入力します。ここは一字一句違ってもエラーになるので気をつけましょう。

スクリーンショット

image.png

Cognitoで動作確認

Cognitoの画面に戻り、「ホストされたUIを起動」リンクをクリックします。「Continue with Google」画面が出てきてGoogleログインが促されましたか?
最終的に https://ALBのFQDN/oauth2/idpresponse?code=... へリダイレクトされれば問題ありません。

スクリーンショット

image.png
image.png

ALBのルール設定

ALBのリスナーからルール設定画面に戻り、ルールを設定します。「詳細設定」の中の「スコープ」は全部揃えるということで3 email profile openid にしておきます。
image.png

動作確認

いよいよ https://ALBのFQDN にアクセスしてみます。
Google認証が求められた上でEC2にアクセスできれば何もかもがうまく行ったということになります。
同時にHTTPS化もできてうれしいですね。

image.png

ログインできるGoogleアカウントを決める

これでGoogleアカウント認証はできたのですが、今のままではどんなアカウントでも認証が通ってしまうため、事実上認証はないに等しい状態です。Lambda関数でカスタムのチェックを行いましょう。

Lambda関数を作る

これから作るLambda関数がやるべきことはたった2つです。(したがってIAM Roleも最低限の権限で良いです)

  1. 引数のオブジェクトからメールアドレスを取り出す
  2. 問題ないなら引数をそのまま返す。認証を拒否するなら例外をスローして異常終了させる

リスナーのスコープとして email profile openid を指定した場合、「サインアップ前」トリガーLambda関数にはこんなオブジェクトが渡されます。

{
  "version": "1",
  "region": "ap-northeast-1",
  "userPoolId": "ap-northeast-1_wycr1qYwL",
  "userName": "google_999999999999999999999",
  "callerContext": {
    "awsSdkVersion": "aws-sdk-unknown-unknown",
    "clientId": "1iaca7vca5rl5dgpqc7ibd7em5"
  },
  "triggerSource": "PreSignUp_ExternalProvider",
  "request": {
    "userAttributes": {
      "cognito:email_alias": "",
      "cognito:phone_number_alias": "",
      "email": "itsumo.itsumo.nemui@gmail.com"
    },
    "validationData": {}
  },
  "response": {
    "autoConfirmUser": false,
    "autoVerifyEmail": false,
    "autoVerifyPhone": false
  }
}

この中からメールアドレスを取り出して、ドメインなり何なりで判定を行えばいいだけです。Node.jsだとどうもうまく行かなかったのでPython 3.7で書きました。

def lambda_handler(event, context):
    print(event)
    email = event["request"]["userAttributes"]["email"]
    if email == "itsumo.itsumo.nemui@gmail.com":
        print("It's me! OK! OK!")
        return event
    raise Exception("bye-bye!")

これをトリガーに設定すれば終わりです。なお、トリガーを設定してから実際にトリガーが呼ばれるようになるまでには若干のタイムラグがあるようです。

スクリーンショット

image.png


  1. ドメインを絞ったGoogleアカウント認証とか 

  2. 逆に言うと、G Suiteを使っていない人は(表示されているのに)「内部」を選ぶことはできません。すっぱい葡萄みたいなオプションですね…… 

  3. 後述のトリガーLambda関数に渡されるパラメーターの形が変わりますが、Googleの場合はemailだけでもopenidだけでもメールアドレスが渡されてきます 

13
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
netebakari
Web開発、TypeScript、AWSあたりを中心に書いたり読んだりします。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
13
Help us understand the problem. What is going on with this article?