Flaskを使用したFacebook ログイン連携 メモ

* Flaskを使用したFacebookログイン連携についてメモする。
  * [Google連携を試したもの](https://qiita.com/KWS_0901/items/28066892d0811e571796)のFacebook版

## 大まかな処理の流れ

1. Facebookへ認可リクエスト(Authorization Request)を行う。

2. ユーザー認証/同意を行い、認可レスポンスを受け取る。

3. 認可レスポンスを使ってトークンリクエストを行う。

4. [アプリアクセストークン](https://developers.facebook.com/docs/facebook-login/access-tokens/#apptokens)の生成を行う。※Googleと違うところ。

5. アクセストークンを検査する。※Googleと違うところ。

   

## 事前準備

* **[Facebookデベロッパーコンソール](https://developers.facebook.com/apps)からアプリケーションを登録する。**
  * クライアントID(アプリID)/app secret 取得(アプリダッシュボード -> 設定 -> ベーシックから確認)
  * クライアントトークン取得(アプリダッシュボード -> 設定 -> 詳細設定から確認)
  * Facebookログイン設定
        * 「アプリに製品を追加」→「Facebookログイン」設定→「ウェブ」選択→サイトURL入力(例：http://localhost:5000) →残りの項目はすべて「次へ」を選択。
        * Facebookログイン -> 設定 -> 「有効なOAuthリダイレクトURI」にhttp://localhost:5000/callbackを入力する。※開発中の場合は、localhostも設定可能。
* **Pythonライブラリインストール**
  * `flask`
    * ウェブアプリ開発用フレームワーク



## 実装

* 認可リクエスト ~ アクセストークン検査までのコード

```python
import hashlib
from flask import Flask, request as r, redirect, session
from urllib import request, parse, error
from http import HTTPStatus
import json
import base64
from pprint import pprint
import os

# クライアント情報(ダッシュボードから確認)
client_id = <YOUR_CLIENT_ID>
client_secret = <YOUR_CLIENT_SECRET>
client_token = <YOUR_CLIENT_TOKEN>
redirect_uri = 'http://localhost:5000/callback'

# Facebook エンドポイント
authorization_base_url = 'https://www.facebook.com/v10.0/dialog/oauth'
token_url = 'https://graph.facebook.com/v10.0/oauth/access_token'
app_token_url = 'https://graph.facebook.com/oauth/access_token'
token_validation_url = 'https://graph.facebook.com/debug_token'

app = Flask(__name__)
app.secret_key = 'session_key'


# 1. Facebookへ認可リクエスト(Authorization Request)を行う。
@app.route("/login")
def login():
    # ステート生成+保存
    state = hashlib.sha256(os.urandom(32)).hexdigest()
    session['state'] = state
    # 認可リクエスト
    return redirect(authorization_base_url+'?{}'.format(parse.urlencode({
        'client_id': client_id,
        'scope': 'email public_profile',
        'redirect_uri': redirect_uri,
        'state': state,
        'response_type': 'code'
    })))


# Authoriztion Redirection and Token Request Endpoint
# 2.ユーザー認証/同意を行い、認可レスポンスを受け取る。
@app.route("/callback")
def callback():
    # ステート検証
    state = r.args.get('state')
    if state != session['state']:
        print("invalid_redirect")
    code = r.args.get('code')

    req, res, body, err_str = '', '', '', ''

    # 3. 認可レスポンスを使ってトークンリクエストを行う。
    token_req_url = token_url+'?{}'.format(parse.urlencode({
        'client_id': client_id,
        'client_secret': client_secret,
        'redirect_uri': redirect_uri,
        'code': code
    }))

    try:
        req = request.Request(token_req_url, method='GET')
        with request.urlopen(req) as res:
            body = res.read()
    except error.HTTPError as err:
        err_str = str(err.code) + ':' + err.reason + ':' + str(err.read())
    except error.URLError as err:
        err_str = err.reason
    access_token = json.loads(body)['access_token']

    # 4. アプリアクセストークンの生成を行う。
    # https://developers.facebook.com/docs/facebook-login/access-tokens/#apptokens
    req_url = app_token_url+'?{}'.format(parse.urlencode({
        'client_id': client_id,
        'client_secret': client_secret,
        'grant_type': 'client_credentials'
    }))
    try:
        req = request.Request(req_url, method='GET')
        with request.urlopen(req) as res:
            body = res.read()
    except error.HTTPError as err:
        err_str = str(err.code) + ':' + err.reason + ':' + str(err.read())
    except error.URLError as err:
        err_str = err.reason
    app_token = json.loads(body)['access_token']

    # 5.アクセストークンを検査する。
    req_url = token_validation_url+'?{}'.format(parse.urlencode({
        'input_token': access_token,
        'access_token': app_token
    }))
    try:
        req = request.Request(req_url, method='GET')
        with request.urlopen(req) as res:
            body = res.read()
    except error.HTTPError as err:
        err_str = str(err.code) + ':' + err.reason + ':' + str(err.read())
    except error.URLError as err:
        err_str = err.reason

    return json.loads(body)


if __name__ == "__main__":
    app.run(debug=True)

```



## 参考情報

* [ログインフローを手動で構築する](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow?locale=ja_JP)
* [アクセストークン](https://developers.facebook.com/docs/facebook-login/access-tokens/#apptokens)