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

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

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

https://dev.classmethod.jp/articles/alb-cognito-user-pool/


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

## Webサーバ

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

```python
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サイトができたとします。



# ログアウトの実装

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

https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/listener-authenticate-users.html#authentication-logout-timeout

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

### ① Cookieの有効期限を-1に設定する。
認証セッションのCookieは、ALBで設定しています。
どのCookieを使っているの？
 → 下の、セッションCookieのところにあります。（※ここはCookie名の確認のみ。）
変更していなかったので、デフォルトですね(^_^;)
「AWSELBAuthSessionCookie-0」「AWSELBAuthSessionCookie-1」が使われていました。
これらCookieの有効期限を、プログラムから-1に設定します。
![スクリーンショット 2021-04-01 16.44.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155165/e5ab4867-8aa8-907b-cb2f-a4bf1ccd7102.png)

### ② クライアントをIdPログアウトエンドポイントにリダイレクトする。
IdPはCognitoです。Cognitoのログアウトエンドポイントは以下に記載あり。

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/logout-endpoint.html

プログラムから、ログアウトエンドポイントに、クライアントIDとログアウトURLを渡してあげればいいわけです。
例のとおり、こんな感じ。
> GET `https://mydomain.auth.us-east-1.amazoncognito.com/logout?client_id=ad398u21ijw3s9w3939&logout_uri=https://myclient/logout`

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

https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/listener-authenticate-users.html#user-claims-encoding

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

## Lambdaにログアウト処理を追加
パスに"logout"があるときにログアウトをさせます。

```python
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のエンドポイントとしました。


![スクリーンショット 2021-04-01 17.49.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155165/1e862ab0-7c2d-f068-8ff0-ae74c397dc6a.png)

# 動作確認

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


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

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

https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/listener-authenticate-users.html

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