- Flask を使用してLINEログイン連携をサポートするためのAPI作成方法についての個人用メモ。
- Google連携用APIを試した時と同様に、Docker起動できる形で過去に作成したLINEログイン検証コードを部分的にAPI化した。
作成するAPI
認可リクエスト作成API
-
LINEへの認可エンドポイントへアクセスするためのURLをパラメータをつけて生成・返却する。
-
state
はAPI呼び出し時に生成し、レスポンスとして返却する。 -
client_id
など固定の属性値は環境変数から取得する。 -
リクエスト例
POST /api/line/auth_request/create HTTP/1.1 Host: localhost:5000
-
レスポンス例
{ "authorization_request_url": "https://access.line.me/dialog/oauth/weblogin?client_id=123456789&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Fcallback&state=ABCDE12345&response_type=code", "state": "ABCDE12345" }
-
トークンリクエスト+ユーザー情報取得API
-
認可レスポンスとしてLINEからのリダイレクトで受け取った認可コードを指定し、トークンリクエスト+ユーザー情報取得を行う。
-
今回は簡略化のため認可リクエストパラメータのDB保存まで実装しておらず、検証処理は行っていないが、stateパラメータの検証は必ず行うこと。
-
リクエスト例
POST /api/line/auth_request/complete HTTP/1.1 Host: localhost:5000 Content-Type: application/json Content-Length: 363 { "code":"XXX" }
-
レスポンス例
{ "displayName": "山田太郎", "userId": "ABCDE12345" }
-
プロジェクト構成
facebook_oidc
└─ docker-compose.yml
└─ line.env
│
└─ be
└─ Dockerfile
└─ requirements.txt
│
└─app
└─ app.py
│
└─ api
└─ __init__.py
│
└─views
└─ line.py
実装
-
docker-compose.yml
※起動時に環境変数ファイル
line.env
を読み込む。version: "3" services: be: container_name: be build: ./be env_file: line.env volumes: - ./be/app:/app ports: - "5000:5000" command: flask run --host 0.0.0.0 --port 5000 tty: true
-
line.env
- 環境変数ファイル。アプリケーション登録時に発行・設定した値を指定する。
LINE_CLIENT_ID=YOUR_CHANNEL_ID LINE_CLIENT_SECRET=YOUR_CHANNEL_SECRET LINE_REDIRECT_URI=YOUR_CALLBACK_URL LINE_AUTHORIZATION_ENDPOINT=https://access.line.me/dialog/oauth/weblogin LINE_TOKEN_ENDPOINT=https://api.line.me/v2/oauth/accessToken LINE_USER_INFO_ENDPOINT=https://api.line.me/v2/profile
-
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.line import line_router from flask_cors import CORS def create_app(): app = Flask(__name__) CORS(app, supports_credentials=True) app.register_blueprint(line_router, url_prefix='/api') return app app = create_app()
-
be/api/views/line.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 Setting line_router = Blueprint('line_router', __name__) # Client Param client_id = os.getenv('LINE_CLIENT_ID') client_secret = os.getenv('LINE_CLIENT_SECRET') redirect_uri = os.getenv('LINE_REDIRECT_URI') # LINE Endpoint authorization_endpoint = os.getenv('LINE_AUTHORIZATION_ENDPOINT') token_endpoint = os.getenv('LINE_TOKEN_ENDPOINT') user_info_endpoint = os.getenv('LINE_USER_INFO_ENDPOINT') app = Flask(__name__) # Create Authorization Request Endpoint @line_router.route("/line/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, '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 @line_router.route("/line/auth_request/complete", methods=['POST']) def complete(): # Parse Req Body jsonData = json.dumps(request.json) req_body = json.loads(jsonData) # Token Request # https://developers.line.biz/ja/docs/line-login/integrate-line-login-v2/#get-access-token token_req_body = parse.urlencode({ 'code': req_body["code"], 'client_id': client_id, 'client_secret': client_secret, 'redirect_uri': redirect_uri, 'grant_type': 'authorization_code' }).encode('utf-8') token_req = req.Request(token_endpoint) err_str = '' try: with req.urlopen(token_req, data=token_req_body) 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'] # Get User Profile # https://developers.line.biz/ja/docs/line-login/managing-users/#get-profile headers = { 'Authorization': 'Bearer ' + access_token } user_info_req = req.Request( user_info_endpoint, headers=headers, method='GET') try: with req.urlopen(user_info_req) as user_info_res: user_info_res_body = user_info_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) return json.loads(user_info_res_body)
-
起動
docker-compose up -d