- Flask を使用してFacebookログイン連携するためのAPI作成方法についてメモする。
- Google連携用APIを試した時と同様に、Docker起動できる形で過去に作成したFacebook検証コードを部分的にAPI化した。
作成するAPI
認可リクエスト作成API
-
Facebookへの認可エンドポイントへアクセスするためのURLをパラメータをつけて生成・返却する。
-
state
はAPI呼び出し時に生成し、レスポンスとして返却する。 -
client_id
など固定の属性値は環境変数から取得する。 - リクエスト例
POST /api/facebook/auth_request/create HTTP/1.1 Host: localhost:5000
- レスポンス例
{ "authorization_request_url": "https://www.facebook.com/v10.0/dialog/oauth?client_id=AAABBBBCCC&scope=email&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Fcallback&state=ABCDE12345&response_type=code", "state": "ABCDE12345" }
-
トークンリクエスト+ユーザー情報取得API
-
認可レスポンスとして受け取った認可コード(
code
)を指定して、トークンリクエスト+ユーザー情報取得を行う。- 今回は簡略化のため認可リクエストパラメータのDB保存まで実装しておらず、検証処理は行っていないが、stateパラメータの検証は必ず行うこと。
- リクエスト
POST /api/facebook/auth_request/complete HTTP/1.1 Host: localhost:5000 Content-Type: application/json Content-Length: 363 { "code":"XXX" }
レスポンス例
{
"id": "1234567890",
"name": "山田太郎"
}
プロジェクト構成
facebook_oidc
└─ docker-compose.yml
└─ facebook.env
│
└─ be
└─ Dockerfile
└─ requirements.txt
│
└─app
└─ app.py
│
└─ api
└─ __init__.py
│
└─views
└─ facebook.py
実装
docker-compose.yml
※起動時に環境変数ファイルfacebook.env
を読み込む。
version: "3"
services:
be:
container_name: be
build: ./be
env_file: facebook.env
volumes:
- ./be/app:/app
ports:
- "5000:5000"
command: flask run --host 0.0.0.0 --port 5000
tty: true
-
facebook.env
- 環境変数ファイル。アプリケーション登録時に発行・設定した値を指定する。
FACEBOOK_CLIENT_ID=YOUR_CLIENT_ID
FACEBOOK_CLIENT_SECRET=YOUR_CLIENT_SECRET
FACEBOOK_SCOPE_LIST=YOUR_SCOPE_LIST
FACEBOOK_REDIRECT_URI=YOUR_REDIRECT_URI
FACEBOOK_AUTHORIZATION_ENDPOINT=https://www.facebook.com/v10.0/dialog/oauth
FACEBOOK_TOKEN_ENDPOINT=https://graph.facebook.com/v10.0/oauth/access_token
FACEBOOK_APP_TOKEN_ENDPOINT=https://graph.facebook.com/oauth/access_token
FACEBOOK_TOKEN_VALIDATION_ENDPOINT=https://graph.facebook.com/debug_token
FACEBOOK_USER_INFO_ENDPOINT=https://graph.facebook.com/v10.0/
-
be/requirements.txt
- Pythonライブラリ一式
Flask
Flask-Cors
be/Dockerfile
FROM python:3.8
RUN mkdir /app
ADD requirements.txt /app
ENV PYTHONUNBUFFERED 1
EXPOSE 5000
WORKDIR /app
RUN pip3 install -r requirements.txt
be/app/app.py
from api import app
if __name__ == '__main__':
app.run()
be/api/__init__.py
from flask import Flask
from .views.facebook import facebook_router
from flask_cors import CORS
def create_app():
app = Flask(__name__)
CORS(app, supports_credentials=True)
app.register_blueprint(facebook_router, url_prefix='/api')
return app
app = create_app()
-
be/api/views/facebook.py
- コントローラー
- ※エラーハンドリング皆無
import hashlib from flask import Flask, Blueprint, request import urllib.parse as parse import urllib.request as req import urllib.error as error import json import os from pprint import pprint # Routing Settings facebook_router = Blueprint('facebook_router', __name__) # Client Param client_id = os.getenv('FACEBOOK_CLIENT_ID') client_secret = os.getenv('FACEBOOK_CLIENT_SECRET') redirect_uri = os.getenv('FACEBOOK_REDIRECT_URI') scope_list = os.getenv('FACEBOOK_SCOPE_LIST') # Facebook Endpoint authorization_endpoint = os.getenv('FACEBOOK_AUTHORIZATION_ENDPOINT') token_endpoint = os.getenv('FACEBOOK_TOKEN_ENDPOINT') app_token_endpoint = os.getenv('FACEBOOK_APP_TOKEN_ENDPOINT') token_validation_endpoint = os.getenv('FACEBOOK_TOKEN_VALIDATION_ENDPOINT') user_info_endpoint = os.getenv('FACEBOOK_USER_INFO_ENDPOINT') app = Flask(__name__) # Create Authorization Request Endpoint @facebook_router.route("/facebook/auth_request/create", methods=['POST']) def create(): # Generate state state = hashlib.sha256(os.urandom(32)).hexdigest() # Create Authz Request URL auth_request_url = authorization_endpoint+'?{}'.format(parse.urlencode({ 'client_id': client_id, 'scope': scope_list, 'redirect_uri': redirect_uri, 'state': state, 'response_type': 'code' })) res_body = { "authorization_request_url": auth_request_url, "state": state } return json.loads(json.dumps(res_body)) # Token Request And Get User Info Endpoint @facebook_router.route("/facebook/auth_request/complete", methods=['POST']) def complete(): # Parse Req Body jsonData = json.dumps(request.json) req_body = json.loads(jsonData) # Exchange Authorization code for Access Token token_req_url = token_endpoint+'?{}'.format(parse.urlencode({ 'client_id': client_id, 'client_secret': client_secret, 'redirect_uri': redirect_uri, 'code': req_body["code"] })) try: token_req = req.Request(token_req_url, method='GET') with req.urlopen(token_req) as token_res: token_res_body = token_res.read() except error.HTTPError as err: err_str = str(err.code) + ':' + err.reason + ':' + str(err.read()) pprint(err_str) except error.URLError as err: err_str = err.reason pprint(err_str) access_token = json.loads(token_res_body)['access_token'] # Generate App Access Token # https://developers.facebook.com/docs/facebook-login/access-tokens/#apptokens app_token_req_url = app_token_endpoint+'?{}'.format(parse.urlencode({ 'client_id': client_id, 'client_secret': client_secret, 'grant_type': 'client_credentials' })) try: app_token_req = req.Request(app_token_req_url, method='GET') with req.urlopen(app_token_req) as app_token_res: app_token_res_body = app_token_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(app_token_res_body)['access_token'] # Validate Access Token token_validation_req_url = token_validation_endpoint+'?{}'.format(parse.urlencode({ 'input_token': access_token, 'access_token': app_token })) try: token_validation_req = req.Request( token_validation_req_url, method='GET') with req.urlopen(token_validation_req) as token_validation_res: token_validation_res_body = token_validation_res.read() except error.HTTPError as err: err_str = str(err.code) + ':' + err.reason + ':' + str(err.read()) pprint(err_str) except error.URLError as err: err_str = err.reason pprint(err_str) # Get User Info # https://developers.facebook.com/docs/graph-api/reference/user user_request_url = user_info_endpoint+'/' + \ json.loads(token_validation_res_body)['data']['user_id']+'?fields=email&access_token='+access_token user_req = req.Request(user_request_url, method='GET') with req.urlopen(user_req) as user_res: user_res_body = user_res.read() return json.loads(user_res_body)
起動
docker-compose up -d