ALBの認証に対するログアウト処理(ALB + Cognito + Lambda)


はじめに

AWSでは、Webサービスの認証機能は、ALBを使うのが超便利ですね!
ここでは、まず準備段階として認証機能を実装します。(→「準備」の段落)
つぎに、ログアウト処理を追加します。(→「ログアウトの実装」の段落)

ALBに認証させる方法は、いろいろな方が書いてくれています。そちらを参考にしてください。
以下、クラスメソッドさんの記事です。

準備

まず、ALBにCognitoを連携し、ログインの実装まで。
上のリンクのとおりに、ALB、Cognitoユーザープールなどは実装してください。
WebサーバはLambdaでベタ書きします。EC2は面倒なので。ログアウトの処理がやりたいので手抜きです。

Webサーバ

Lambda関数を、Python3.8で実装する。

import json

def lambda_handler(event, context):

    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'text/html; charset=utf-8'
        },
        'body': '''
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>title</title>
  </head>
  <body>
    テスト
  </body>
</html>
'''
    }

ターゲットグループの作成

ターゲットをこのLambda関数にしたターゲットグループを作成します。

Cognitoユーザープールの作成、ALBの作成、ターゲットと認証の登録

上にあるリンク先のとおりに。
ここまでで、認証をもったWebサイトができたとします。

ログアウトの実装

方法

認証からログアウトさせる方法は、以下に記載があります。

認証セッション Cookie の有効期限を -1 に設定し、クライアントを IdP ログアウトエンドポイントにリダイレクトする

① Cookieの有効期限を-1に設定する。

認証セッションのCookieは、ALBで設定しています。
どのCookieを使っているの?
→ 下の、セッションCookieのところにあります。(※ここはCookie名の確認のみ。)
変更していなかったので、デフォルトですね(^_^;)
「AWSELBAuthSessionCookie-0」「AWSELBAuthSessionCookie-1」が使われていました。
これらCookieの有効期限を、プログラムから-1に設定します。

② クライアントをIdPログアウトエンドポイントにリダイレクトする。

IdPはCognitoです。Cognitoのログアウトエンドポイントは以下に記載あり。

プログラムから、ログアウトエンドポイントに、クライアントIDとログアウトURLを渡してあげればいいわけです。
例のとおり、こんな感じ。

GET https://mydomain.auth.us-east-1.amazoncognito.com/logout?client_id=ad398u21ijw3s9w3939&logout_uri=https://myclient/logout

クライアントIDは、ALBがヘッダーに渡してくれているので、そこから取り出します。

ユーザークレーム(x-amzn-oidc-data)のJWTヘッダーにクライアントID(client_id)があります。
ログアウトURLは、ログアウト後に遷移するURLのことです。ここではまたログイン画面を出したいと思います。つまり、WebサーバにアクセスするURL(=ALBのエンドポイント)とします。

Lambdaにログアウト処理を追加

パスに"logout"があるときにログアウトをさせます。

import json
import base64
import urllib.parse

def lambda_handler(event, context):
    # print(event)
    headers = event['headers']
    # JwtヘッダをDict型に変換
    encoded_jwt = headers['x-amzn-oidc-data']
    jwt_headers = encoded_jwt.split('.')[0]
    decoded_jwt_headers = base64.urlsafe_b64decode(jwt_headers)
    decoded_jwt_headers = decoded_jwt_headers.decode("utf-8")
    decoded_json = json.loads(decoded_jwt_headers)

    path = event['path']
    # ログアウト処理
    if path.find('logout') > 0:
      logout_uri = 'https://<ELBのドメイン>.ap-northeast-1.elb.amazonaws.com'

      redirect_url = 'https://<ユーザープールのドメインのプレフィックス>.auth.ap-northeast-1.amazoncognito.com/logout?client_id=' \
        + decoded_json['client'] + '&logout_uri=' + urllib.parse.quote(logout_uri, encoding='utf-8')
      return {
        'statusCode': 302,
        'headers': {
          'Set-Cookie': 'AWSELBAuthSessionCookie-0=; max-age=0',
          'Set-CookiE': 'AWSELBAuthSessionCookie-1=; max-age=0',
          'Access-Control-Allow-Origin': logout_uri,
          'Access-Control-Allow-Methods': 'GET',
          'Location': redirect_url
        }
      }

    return {
        'statusCode': 200,
        'headers': {
          'Content-Type': 'text/html; charset=utf-8'
        },
        'body': '''
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>title</title>
  </head>
  <body>
    テスト
  </body>
</html>
'''
    }

<>の箇所は、自分の値を設定します。

ユーザープールへのリダイレクトの追加

ユーザープールのアプリクライアントに、サインアウト(ログアウト)のURLを設定します。上に書いたように、ALBのエンドポイントとしました。

動作確認

ELBのDNS名にブラウザでアクセスしてログインからサイトの表示
https://<ELB名>.ap-northeast-1.elb.amazonaws.com
ログアウトするとき → ログイン画面に遷移
https://<ELB名>.ap-northeast-1.elb.amazonaws.com/logout

おわりに

100%理解していないところもありますが、動くので掲載しました!!

  • ユーザークレームの署名の検証は省略しています。本来は、必要に応じて検証を実施すべきです。(コードが長くなったのと、ライブラリのインポートの説明が必要になるので、省略しました。)

  • リダイレクトのステータスコードは302にしていますが、301とか、ちゃんと理解できてないです(^_^;)
  • MacOS+Safariで確認しています。MacOS+Chromeだと、httpsの自己証明書ではALBのドメインにアクセス出来ませんでした。
  • リージョンはTokyo(ap-northeast-1)に固定してしまっています。