- Flask を使用してZoom OAuth連携をサポートするためのAPI作成方法についての個人用メモ。
- Google連携用APIを試した時と同様に、Docker起動できる形で部分的にAPI化した。
作成するAPI
①認可リクエスト作成API
-
Zoomへの認可エンドポイントへアクセスするためのURLをパラメータをつけて生成・返却する。
-
client_id
など固定の属性値は環境変数から取得する。 -
state
はAPI呼び出し時に生成し、レスポンスとして返却する。 - リクエスト例
POST /api/zoom/auth_request/create HTTP/1.1 Host: localhost:5000
- レスポンス例
{ "authorization_request_url": "https://zoom.us/oauth/authorize?client_id=...&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Fzoom%2Fcallback&state=...&response_type=code", "state":"3708b06366830c67818c1d441c5964fa9ac3f0340f4690c3249b8d42d8610855" }
-
②トークンリクエスト+ユーザー情報取得API
-
認可レスポンスとしてzoomからのリダイレクトで受け取った認可コードを指定し、トークンリクエストとユーザー情報取得を行う。
- 今回は簡略化のため認可リクエストパラメータのDB保存まで実装しておらず、検証処理は行っていない。実運用では検証処理を入れることを推奨。
- リクエスト例
POST /api/zoom/auth_request/complete HTTP/1.1 Host: localhost:5000 Content-Type: application/json Content-Length: 52 { "code":"..." }
- レスポンス例(ユーザー情報)
{ "account_id": "...", "cms_user_id": "", "created_at": "2021-07-03T08:32:58Z", "dept": "", "email": "hogehoge@example.com", "first_name": "...", "group_ids": [], "host_key": "...", "id": "...", "im_group_ids": [], "jid": "....", "job_title": "", "language": "jp-JP", "last_login_time": "2021-07-03T08:32:58Z", "last_name": "...", "location": "", "login_types": [ 1, 100 ], "personal_meeting_url": "...", "phone_country": "", "phone_number": "", "pic_url": "...", "pmi": ..., "role_id": "0", "role_name": "Owner", "status": "active", "timezone": "", "type": 1, "use_pmi": false, "verified": 0 }
事前作業
- アプリケーション登録を行い、クライアントID/シークレットの発行、リダイレクトURIの登録を行う。
プロジェクト構成
facebook_oidc
└─ docker-compose.yml
└─ zoom.env
│
└─ be
└─ Dockerfile
└─ requirements.txt
│
└─app
└─ app.py
│
└─ api
└─ __init__.py
│
└─views
└─ zoom.py
実装
docker-compose.yml
※起動時に環境変数ファイルzoom.env
を読み込む。
version: "3"
services:
be:
container_name: be
build: ./be
env_file: zoom.env
volumes:
- ./be/app:/app
ports:
- "5000:5000"
command: flask run --host 0.0.0.0 --port 5000
tty: true
-
zoom.env
- 環境変数ファイル。アプリケーション登録時に発行・設定した値を指定する。
CLIENT_ID=YOUR_CLIENT_ID
CLIENT_SECRET=YOUR_CLIENT_SECRET
REDIRECT_URI=http:YOUR_CALLBACK_URL
AUTHORIZATION_ENDPOINT=https://zoom.us/oauth/authorize
TOKEN_ENDPOINT=https://zoom.us/oauth/token
USERINFO_ENDPOINT=https://zoom.us/v2/users/me
-
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.zoom import zoom_router
from flask_cors import CORS
def create_app():
app = Flask(__name__)
CORS(app, supports_credentials=True)
app.register_blueprint(zoom_router, url_prefix='/api')
return app
app = create_app()
-
be/api/views/zoom.py
- コントローラー
- ※エラーハンドリング皆無
from flask import Flask, Blueprint, request
import hashlib
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
zoom_router = Blueprint('zoom_router', __name__)
# Client Param
client_id = os.getenv('CLIENT_ID')
client_secret = os.getenv('CLIENT_SECRET')
redirect_uri = os.getenv('REDIRECT_URI')
# Zoom Endpoint
authorization_endpoint = os.getenv('AUTHORIZATION_ENDPOINT')
token_endpoint = os.getenv('TOKEN_ENDPOINT')
userinfo_endpoint = os.getenv('USERINFO_ENDPOINT')
app = Flask(__name__)
# Create Authorization Request Endpoint
@zoom_router.route("/zoom/auth_request/create", methods=['POST'])
def create():
state = hashlib.sha256(os.urandom(32)).hexdigest()
# Create Authz Request URL
# https://zoom.us/oauth/authorize?response_type=code&client_id=...&redirect_uri=...&state=...
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
@zoom_router.route("/zoom/auth_request/complete", methods=['POST'])
def complete():
# Parse Req Body
jsonData = json.dumps(request.json)
req_body = json.loads(jsonData)
# Token Request
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 f:
token_res = f.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)['access_token']
# Get User Info
# https://marketplace.zoom.us/docs/api-reference/zoom-api/users/user
headers = {
'Authorization': 'Bearer ' + access_token
}
user_info_req = req.Request(
userinfo_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