皆様こんにちは。
今回はpythonのライブラリFlaskを用いて、LINEログインv2.1に対応した、
テストアプリを作りたいと思います。
1.準備
必要なライブラリをインストールします。
今回は以下のライブラリを利用します。
Flask
requests
Flask-Session==0.3.0
uwsgi
gunicorn
argparse
2.stateの発行
Pythonのライブラリ https://pythonhosted.org/Flask-Session/ を利用します。
今回はテストアプリなので、単純なロジックでランダムな32文字を作成し、
これをsessionとしています。
import random
import string
# セッションのためのユニークキーの作成
def randomstate(n):
randlst = [random.choice(string.ascii_letters + string.digits)
for i in range(n)]
return ''.join(randlst)
# メインページ。sessionライブラリでstate管理
@app.route('/', methods=['GET'])
def Mainpage():
# randomstate(n)でstate長を制御
session["state"] = randomstate(32)
return render_template('login.html',
state=session["state"]
)
3.LINEログインの認可リクエストの作成
POSTした内容を元にLINEログインの認可リクエストを作成します。
https://developers.line.biz/ja/docs/line-login/integrate-line-login/#making-an-authorization-request
scopeが複数してされた場合であっても、対応できるようにscopeの値をループして取り出しています。
from argparse import ArgumentParser
import json
import urllib.request
import requests
from flask import Flask, request, abort, render_template, jsonify, redirect, session
# LINEログインの認可リクエストの生成
@app.route('/login', methods=['POST'])
def authorizeReq():
scopes = ""
i = 0
for key in request.form.getlist("ScopeValue"):
if (i < 1):
i = i + 1
scopes = key
else:
scopes = scopes+" "+key
queries = {}
queries['response_type'] = 'code'
queries['client_id'] = request.form['ChannelIdValue']
queries['redirect_uri'] = request.form['redirect_uriValue']
queries['scope'] = scopes
queries['state'] = request.form['stateValue']
queries['prompt'] = request.form['promptValue']
queries['bot_prompt'] = request.form['bot_promptValue']
queries['nonce'] = request.form['nonceValue']
queries['max_age'] = request.form['max_ageValue']
queries['ui_locales'] = request.form['ui_localesValue']
authorize_url = 'https://access.line.me/oauth2/v2.1/authorize?' + \
urllib.parse.urlencode(queries)
return redirect(authorize_url)
4.stateの検証
単純にstateの検証および、エラー時のハンドリング(認可リクエストをキャンセルした場合)を行います。
from argparse import ArgumentParser
import json
import urllib.request
import requests
from flask import Flask, request, abort, render_template, jsonify, redirect, session
@app.route('/callback', methods=['GET'])
def Callbackpage():
state = request.args.get('state')
error = request.args.get('error')
code = request.args.get('code')
# ローカルで試す場合は uri = request.base_url
# 外部サーバで試す場合はuriにHTTTPSから始まるアドレスを指定
uri = "コールバックURLを入力"
error_description = request.args.get('error_description')
# エラーハンドリングよりも先にstateの検証を行う
expected_state = session.get('state')
if state != expected_state:
return "[Error] state does not match", 400
# 認可リクエストをキャンセルした場合などのerrorの制御
if error:
return "[Error] Not Logined: " + error + "\n" + error_description, 400
return render_template('callback.html',
code=code,
state=state,
uri=uri
)
5.上記内容を元に実装したテストアプリ
テストアプリではアクセストークンおよびIDトークンのデコードも実装しています。
https://myucy-login.herokuapp.com/
6.メモ
Flask-Sessionライブラリを利用することで非常に簡単にCSRF対策ができるので
ぜひ実装してください。
そういえば、認証バッジ付きの公式アカウントのLINEログインでもstateが固定なアカウントを
ちらほら見かけますが、あれは大丈夫なんでしょうか?