PythonでAWS Cognito認証 with Facebook認証

以前PythonでAWS Cognito認証を使う記事を書きましたが、UserPoolにユーザーを登録しておく必要がありました。認証画面を作るのが面倒くさい上に他には使いまわしませんとかの個人情報保護規定の表示とか、まともに公開しようとするととにかく面倒くさい(´・ω・｀)

仕事なら頑張ってやってくださいといったところですが(文書関係は書いてくれる部署に丸投げだ)、個人でカジュアルにWebサービス公開したいときにはやってられませんよね。というわけでやっぱり外部認証は取り入れたいと思い今回はFacebook認証でAWS Cognito認証をおこなってみました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/140718/027241ba-9f9b-b15b-d976-66bb7140086e.png)


# 事前準備
まずはFacebookで認証できるようにしなければなりません。Facebookの開発者向けページ(日本語)にやり方が書いてあるので、上から順番にやっていくだけでほぼ終わります。
https://developers.facebook.com/docs/apps/register?locale=ja_JP#developer-account

大雑把に次のような感じです

* Facebook開発者登録
* Facebookアプリの作成
* FacebookSDKでアクセス

それでは順番に見ていきます。

## Facebook開発者登録
次へを押すだけです。Facebook for Developersアカウントの登録完了メールが送られてきます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/140718/88f33041-9a91-65c7-72ee-5296b39293d9.png)

## Facebookアプリの作成
「新しいアプリを作成する」というリンクからアプリの作成が行えます。いきなりアプリIDが振られるのでこれを覚えておきます。認証を使うだけならほぼ設定は要りません。

アプリが作成できたら「製品を追加」というところに並んでいる「Facebookログイン」を追加します。右下の設定ボタンを押せば追加されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/140718/3cdf46a7-ede2-df52-e10c-88dfac7489f0.png)

## FacebookSDKでアクセス
クイックスタートに従って作っていきます。今回はウェブを選択したとして進めますので、JavaScriptによるアクセスになります。

といってもほとんどコード書いてくれているので、コピペしてちょびっと修正するだけです。

```javascript
  window.fbAsyncInit = function() {
    FB.init({
      appId      : '{your-app-id}',
      cookie     : true,
      xfbml      : true,
      version    : '{latest-api-version}'
    });

    FB.AppEvents.logPageView();   
  };

  (function(d, s, id){
     var js, fjs = d.getElementsByTagName(s)[0];
     if (d.getElementById(id)) {return;}
     js = d.createElement(s); js.id = id;
     js.src = "https://connect.facebook.net/en_US/sdk.js";
     fjs.parentNode.insertBefore(js, fjs);
   }(document, 'script', 'facebook-jssdk'));
```
your-app-idやlatest-api-versionは書き換えて使います。your-app-idの部分に覚えておいたアプリIDを、latest-api-versionはグラフAPIのバージョンで書いている時点では'v3.0'が一番新しかったのでこれを使いました。バージョンについては https://developers.facebook.com/docs/graph-api/changelog/ を参照してください。バージョンごとに期限があるのでとりあえず記事通りv3.0にしておけばいいや、とかはダメです。

クイックスタートの次の項目に進むと、

```javascript
FB.getLoginStatus(function(response) {
    statusChangeCallback(response);
});
```

を呼び出せとあるので、

```javascript
function checkLoginState() {
    FB.getLoginStatus(function(response) {
        statusChangeCallback(response);
    });
}
```

のように関数化しておいて、

```heml
<button onClick="checkLoginState()">認証</button>
```

のようにして呼び出して使います。

※SDKを動的にロードしているので、ボタンが押されたタイミングでロードが間に合って無いときがある気がします(´・ω・｀)

認証を行った後の処理であるstatusChangeCallback関数は、自分で作成します。

例えばこんな感じです。

```javascript
function statusChangeCallback(response){
    // response.authResponse.accessTokenがあれば成功とみなせる
    if(response && response.authResponse && response.authResponse.accessToken){
        // 認証に成功したときの処理。ここではユーザー名を取得してからAWS認証を実行している
        // ユーザー名が必要なければ、response.authResponse.accessToken をPOSTするだけでいい
        FB.api("/" + response.authResponse.userID, function(res) {
            var user_name = res.name;  // Facebook上の名前をPOSTで送ることができる

            // TODO: ここでAjaxで response.authResponse.accessToken をPOSTする
            // TODO: POSTした結果(認証の成否)を受け取ってリダイレクト先に飛ばす
        });
    }else{
        // Facebookログイン画面のポップアップを表示する
        FB.login(statusChangeCallback);
    }
}
```

FB.login() を使うと、Facebookが用意してくれたログイン画面が現れますのでとてもお手軽です。

# AWSで認証する

## IAMの用意

[前の記事](https://qiita.com/jp_ibis/items/4fffb3c924504f0ce6fb)と同様PythonでAWSでアクセスするために、IAMでアクセス用のユーザーを作っておきます。ロールはAmazonCognitoReadOnlyで大丈夫です。

## AWS Cognitoの設定

AWSのCognitoの画面で「IDプールの管理」から新しいIDプールの作成をおこないます。ロールの設定はデフォルトのままで問題ないです。

IDプール名を適当に決めたら、IDプールのIDと書かれているところのIDを覚えておきます。ap-northeast-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX のように表示されているやつです。

認証プロバイダーは今回はFacebookを選択します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/140718/018b5f3d-c5cd-02d0-1b80-2dca7d718c9a.png)

Facebookだけでなく色々な認証と連携できますので、自分でユーザー情報管理しなくてもほとんどのユーザーに対応できそうですね。FacebookアプリIDのところにFacebook上で作業してたときに覚えておいたアプリIDを入力します。

作成ボタンを押して完了してください。

## Pythonで認証する

PythonからAWSにアクセスするにはboto3を使用します。普通にpipで入ります。

```bash
pip install --upgrade boto3
```
※記事を書いた時点では1.7.48が入りました

cognito-identityでクライアントオブジェクトを作ってget_id()が成功したら認証成功です。認証に失敗した時は例外がでるので、try-exceptで囲ってアプリケーションごと落ちないようにしてください。

あとAWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEYはくれぐれもハードコーディングしたりしてVCSに登録してしまわないうように。

```python
import sys
import boto3

def aws_cognito_with_facebook(facebook_access_token):
	try:
		aws_client = boto3.client('cognito-identity',
			region_name = "ap-northeast-1",
			aws_access_key_id = "ここにAWS_ACCESS_KEY_IDを設定します",
			aws_secret_access_key = "ここにAWS_SECRET_ACCESS_KEYを設定します",
		)

		return aws_client.get_id(
			IdentityPoolId = "ここにAWS Cognitoで作ったIDプールのIDを設定します",
			Logins = {
				"graph.facebook.com": facebook_access_token
			}
		)
	except:
		print(sys.exc_info())
		return None
```

後はPOSTで受け取ったFacebookのアクセストークンを引数に入れて関数を呼び出すだけです。認証が成功したら認証情報の入った連想配列が返りますし失敗したらNoneが返ります。

FacebookのアクセストークンはSession情報あたりに保存しておいて、認証のときはその値を使うようにしておくとよさそうですね。複数の外部認証にも対応できそうです。

```python
# ※FacebookアクセストークンをPOSTで受け取ったら、session["cognito"]["facebook"]に保存しておく
# 他の認証でトークンを受け取ったらsession["cognito"]["認証先の名前"]で保存しておく

# 複数の認証に対応した認証関数
def aws_cognito_auth():
    result = None
    if "facebook" in session["cognito"]:
        result = aws_cognito_with_facebook(session["cognito"]["facebook"])
    else:
        # 他の認証も行える

    return result
```

とりあえず今回はFacebookだけ。

認証情報を自分で管理する必要がないっていいですね！（ ＾ω＾）