はじめに
Amazon Cognito で Managed Login 機能を 2024 年 11 月に提供を開始しました。今までの Hosted UI が進化してリッチな見た目になり、日本語対応が可能で、MFA やパスキーの登録や認証機能が付属されるようになり、アプリケーション側の実装負担を削減できる、といったうれしさがあります。
Hosted UI のログイン画面
Managed Login のログイン画面
今回は、ALB と Cognito の Managed Login を連携する設定方法を紹介します。
構成図
事前設定
テーマとは外れるので、以下の内容は記事の中では触れません。
- EC2 インスタンスで Nginx と Flask を動かす
- ALB と EC2 を紐づける
- ACM と連携して HTTPS を構成
Cognito User Pool の作成
Cognito User Pool を作成します。
名前などは適当に指定します。
Callback URL の指定
作成した Cognito Userpool で、Callback URL を指定します。これは、Cognito がログイン処理を行ったあとに表示する Web サイトの URL です。ALB に指定しているドメインを指定します。
Cognito Userpool の画面の中で、作成した App Client を開きます。
Login pages のタブから Edit を押します。
エンドユーザーに提供する Web サイトの URL である、ALB のドメイン名を指定します。ドメインの末尾に /oauth2/idpresponse
を付与します。これは、ALB 側と Cognito 連携する際に指定する、固定されているパスです。
Custom Domain の指定
任意の設定項目になりますが、Cognito のログイン URL を独自のドメインで指定できます。組織内で Managed Login を利用する場合には、独自のドメインを利用したいことが多いと思います。
Domain のメニューから、Create custom domain を選択します。
エンドユーザーに提供したい Web サイトの URL が、以下の URL だとします。
今回の手順では、Cognito の Custom Domain は、auth
サブドメインを追加するものとします。
なので、以下のパラメーターで指定をします。ACM の証明書設定が別途設定しておく必要があります。
Create ボタンを押すと、以下の画面に変わります。CNAME レコードを作成するように、という指示があります。
以下のように auth サブドメインの大して、 CNAME レコードを作成します。以下の画像は、Route 53 の画面ですが、独自の DNS サーバーに合わせて変更ください。
ALB と Cognito の連携設定
ALB と Cognito を連携する設定を入れていきます。Listener のルールを指定します。
Authentication から、Cognito の連携設定を入れていきます。
動作確認
動作確認をしていきます。以下の URL にアクセスします。
Cognito の Managed Login の画面が自動的に開かれます。Create an account を選択します。
自分自身のメールアドレスやパスワードなどを指定します。
指定したメールアドレスに Code が届きます。
Code を入力します。
ログインされました。Nginx が稼働しているサーバーが受け取る、Request Header を表示させています。
EC2 インスタンスが受け取る JWT について
ALB と Cognito を連携しているとき、ALB から EC2 インスタンスへ送信される HTTP Request の Request Header の X-Amzn-Oidc-Data
に JWT (JSON Web Token) が付与されている。これを利用して正常な JWT の検証をしながら、ユーザーのメールアドレスなどを確認可能
以下がこちらのサイトをつかって JWT を decode したときの例。アクセスしたユーザーのメールアドレスを確認ができるので、アプリケーション側で認可処理を実装可能。
付録 : Cookie
ALB と Cognito を連携したあと、ユーザーが Managed Login を開いて、ログイン成功した場合、自動的にブラウザ側に Cookie が保存される。この Cookie を削除すると、ログアウトされている状態として扱われる。
付録 : Nginx と Flask の導入方法
以下に簡単にメモをする。
Nginx をインストール
sudo dnf install nginx
起動
sudo systemctl restart nginx
sudo systemctl enable nginx
必要なパッケージのインストール
sudo dnf install python3-pip
pip3 install flask gunicorn
アプリケーションディレクトリの作成
mkdir ~/flaskapp
cd ~/flaskapp
アプリケーションコードの作成
cat << EOF > app.py
import json
import base64
from flask import Flask, request, redirect, make_response
from datetime import datetime
app = Flask(__name__)
COGNITO_DOMAIN = "https://auth.cognito-nginx01.sugiaws.tokyo"
CLIENT_ID = "5ehctf8ikmlvo1grqk7id5v4a2"
LOGOUT_URI = "https://cognito-nginx01.sugiaws.tokyo"
def decode_jwt(token):
# JWTのペイロード部分(2番目の部分)を取得
payload = token.split('.')[1]
# Base64デコード(パディングの調整が必要な場合がある)
payload += '=' * ((4 - len(payload) % 4) % 4)
decoded = base64.b64decode(payload)
# JSONとしてパース
return json.loads(decoded)
@app.route('/logout')
def logout():
logout_url = f"{COGNITO_DOMAIN}/logout?client_id={CLIENT_ID}&logout_uri={LOGOUT_URI}"
response = make_response(redirect(logout_url))
# Delete ALB auth cookies by setting them to empty and expired
response.set_cookie('AWSELBAuthSessionCookie-0', '', expires=0, domain=None, path='/', httponly=True)
response.set_cookie('AWSELBAuthSessionCookie-1', '', expires=0, domain=None, path='/', httponly=True)
return response
@app.route('/')
def home():
headers = dict(request.headers)
headers.pop('X-Forwarded-For', None)
formatted_headers = '\n'.join([f"{key}: {value}" for key, value in headers.items()])
# JWTからメールアドレスを抽出
jwt_data = headers.get('X-Amzn-Oidc-Data')
email = "Not found"
if jwt_data:
try:
decoded = decode_jwt(jwt_data)
email = decoded.get('email', 'Email not found in JWT')
except Exception as e:
email = f"Error decoding JWT: {str(e)}"
return f"""
<h1>Request Headers</h1>
<pre>{formatted_headers}</pre>
<h2>Email from JWT</h2>
<p>{email}</p>
<a href="/logout">Logout</a>
"""
if __name__ == '__main__':
app.run()
EOF
Gunicorn サービスファイルの作成
sudo tee /etc/systemd/system/flaskapp.service << EOF
[Unit]
Description=Gunicorn instance to serve flask application
After=network.target
[Service]
User=ec2-user
Group=nginx
WorkingDirectory=/home/ec2-user/flaskapp
Environment="PATH=/home/ec2-user/.local/bin"
ExecStart=/home/ec2-user/.local/bin/gunicorn --workers 3 --bind unix:flaskapp.sock -m 777 app:app
[Install]
WantedBy=multi-user.target
EOF
Nginx の設定ファイル作成
sudo tee /etc/nginx/conf.d/flaskapp.conf << EOF
server {
listen 80;
server_name _;
location / {
proxy_pass http://unix:/home/ec2-user/flaskapp/flaskapp.sock;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
EOF
ec2-user を nginx グループに追加
sudo usermod -a -G nginx ec2-user
flaskapp ディレクトリに nginx ユーザーがアクセスできるようにする
sudo chmod 755 /home/ec2-user
sudo chmod 755 /home/ec2-user/flaskapp
Gunicorn サービスの起動
sudo systemctl daemon-reload
sudo systemctl restart flaskapp
sudo systemctl enable flaskapp
status
$ sudo systemctl status flaskapp
● flaskapp.service - Gunicorn instance to serve flask application
Loaded: loaded (/etc/systemd/system/flaskapp.service; disabled; preset: disabled)
Active: active (running) since Sun 2024-12-22 00:28:08 UTC; 4s ago
Main PID: 62726 (gunicorn)
Tasks: 4 (limit: 9346)
Memory: 52.4M
CPU: 495ms
CGroup: /system.slice/flaskapp.service
├─62726 /usr/bin/python3 /home/ec2-user/.local/bin/gunicorn --workers 3 --bind unix:flaskapp.sock -m 007 app:app
├─62728 /usr/bin/python3 /home/ec2-user/.local/bin/gunicorn --workers 3 --bind unix:flaskapp.sock -m 007 app:app
├─62729 /usr/bin/python3 /home/ec2-user/.local/bin/gunicorn --workers 3 --bind unix:flaskapp.sock -m 007 app:app
└─62730 /usr/bin/python3 /home/ec2-user/.local/bin/gunicorn --workers 3 --bind unix:flaskapp.sock -m 007 app:app
Dec 22 00:28:08 ip-10-0-1-135.ap-northeast-1.compute.internal systemd[1]: Started flaskapp.service - Gunicorn instance to serve flask application.
Dec 22 00:28:08 ip-10-0-1-135.ap-northeast-1.compute.internal gunicorn[62726]: [2024-12-22 00:28:08 +0000] [62726] [INFO] Starting gunicorn 23.0.0
Dec 22 00:28:08 ip-10-0-1-135.ap-northeast-1.compute.internal gunicorn[62726]: [2024-12-22 00:28:08 +0000] [62726] [INFO] Listening at: unix:flaskapp.sock (62726)
Dec 22 00:28:08 ip-10-0-1-135.ap-northeast-1.compute.internal gunicorn[62726]: [2024-12-22 00:28:08 +0000] [62726] [INFO] Using worker: sync
Dec 22 00:28:08 ip-10-0-1-135.ap-northeast-1.compute.internal gunicorn[62728]: [2024-12-22 00:28:08 +0000] [62728] [INFO] Booting worker with pid: 62728
Dec 22 00:28:08 ip-10-0-1-135.ap-northeast-1.compute.internal gunicorn[62729]: [2024-12-22 00:28:08 +0000] [62729] [INFO] Booting worker with pid: 62729
Dec 22 00:28:08 ip-10-0-1-135.ap-northeast-1.compute.internal gunicorn[62730]: [2024-12-22 00:28:08 +0000] [62730] [INFO] Booting worker with pid: 62730
Nginx の再起動
sudo systemctl restart nginx
付録 : 日本語の表示について
付録 : セルフサインアップの停止
Managed Login は、デフォルトでユーザー側のセルフアインアップ機能が提供されているので、これを停止することが可能。
無くなっている
検証を通じてわかったこと
- ALB と Cognito を連携して Managed Login を利用したログイン画面を提供可能
- Managed Login で認証成功したとき、ブラウザ側に Cookie が保存される。この Cookie の名前は、デフォルトで
AWSELBAuthSessionCookie
となっているが、任意の名前を指定可能