LoginSignup
3
1

More than 3 years have passed since last update.

【LINEログイン】Flaskでstateの検証する

Last updated at Posted at 2020-04-20

皆様こんにちは。

今回は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が固定なアカウントを
ちらほら見かけますが、あれは大丈夫なんでしょうか?

7.おまけ

ソースコード
https://github.com/myucy/line-login-v2.1-tester

3
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1