[PYTHON] [LINE login] Verify state with Flask

Hello everyone.

This time, using the python library Flask, it corresponds to LINE login v2.1, I would like to make a test app.

https://developers.line.biz/ja/docs/line-login/integrate-line-login/

1. Preparation

Install the required libraries. This time, we will use the following libraries.

Flask
requests
Flask-Session==0.3.0
uwsgi
gunicorn
argparse

https://github.com/myucy/line-login-v2.1-tester/blob/master/requirements.txt

2. Issuance of state

Use the Python library https://pythonhosted.org/Flask-Session/. Since this is a test app, I created 32 random characters with simple logic and This is the session.


import random
import string

#Creating a unique key for a session

def randomstate(n):
    randlst = [random.choice(string.ascii_letters + string.digits)
               for i in range(n)]
    return ''.join(randlst)

#Main page. state management with session library
@app.route('/', methods=['GET'])
def Mainpage():
    # randomstate(n)Control state length with
    session["state"] = randomstate(32)
    return render_template('login.html',
                           state=session["state"]
                           )

3. Create a LINE login authorization request

Create a LINE login authorization request based on the POSTed content. https://developers.line.biz/ja/docs/line-login/integrate-line-login/#making-an-authorization-request Even if there are multiple scopes, the scope value is looped out so that it can be handled.

from argparse import ArgumentParser
import json
import urllib.request
import requests

from flask import Flask, request, abort, render_template, jsonify, redirect, session
#Generate LINE login authorization request
@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 verification

It simply validates the state and handles errors (when the authorization request is canceled).

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')
    #If you want to try it locally, uri= request.base_url
    #When trying with an external server, specify the address starting with HTTTPS in uri
    uri = "Enter the callback URL"

    error_description = request.args.get('error_description')

    #Verify state before error handling
    expected_state = session.get('state')
    if state != expected_state:
        return "[Error] state does not match", 400


    #Control of errors such as when an authorization request is canceled
    if error:
        return "[Error] Not Logined: " + error + "\n" + error_description, 400

    return render_template('callback.html',
                           code=code,
                           state=state,
                           uri=uri
                           )

5. A test app implemented based on the above contents

The test app also implements access token and ID token decoding. https://myucy-login.herokuapp.com/

6. Note

By using the Flask-Session library, CSRF countermeasures can be taken very easily. Please implement it. Speaking of which, even if you log in to LINE, which is an official account with an authentication badge, you should have an account with a fixed state. You can see it here and there, but is that okay?

7. Bonus

Source code https://github.com/myucy/line-login-v2.1-tester

Recommended Posts

[LINE login] Verify state with Flask
SNS made with Flask Flask (login process by flask_login)
Skip Line on successful SSH login with ifttt
Make a morphological analysis bot loosely with LINE + Flask
IP restrictions with Flask
Hello world with flask
Programming with Python Flask
Twitter posting client made with Flask with simple login function
I made LINE-bot with Python + Flask + ngrok + LINE Messaging API
Deploy Flask with ZEIT Now
Implement login function with django-allauth
Touch Flask + run with Heroku
Hello World with Flask + Hamlish
Unit test flask with pytest
API with Flask + uWSGI + Nginx
SNS Flask (Ajax) made with Flask
Web application development with Flask
View flask coverage with pytest-cov
Easy proxy login with django-hijack
Web application with Python + Flask ② ③
File upload with Flask + jQuery
Login with django rest framework
Web application with Python + Flask ④