[Python] OAuth認証でTwitter連携/ログインを実装する

# :star: 概要
- PythonでTwitter連携を実装しようとしてみたところ、あんまり例がなかったのでつくってみました
- ドキュメントを読めば分かるという人はこちら → [Implementing Sign in with Twitter](https://developer.twitter.com/en/docs/twitter-for-websites/log-in-with-twitter/guides/implementing-sign-in-with-twitter)
- ざっくりいうと、こういうやつのこと
![example](https://syncer.jp/twitter-how-to-setting-connect-application/medias/twitter-how-to-setting-connect-application-info-01.png)

# :star: 流れ
- Twitterのアプリを作成する
- リクエストトークンを取得して連携画面のURLを生成する
- 連携後のパラメーターを使ってアクセストークンを生成する
- アクセストークンを使ってユーザーのツイートを取得する


## :pencil2: Twitterのアプリを作成する
- [Twitter Application Management](https://apps.twitter.com/) の右上の"Create New App"からアプリを作成します
- Name, Descripiton, Website は適宜入力してください（認証画面に表示される内容です）
- CallbackURLsに連携ボタンを押した後に遷移するURLを設定する必要があります（Twitter連携を実装する場合は必須）
- このCallbackURLsは前方一致ではなく**完全一致**でないといけなくなりました（2018/06頃より）
- アプリ作成後、次の画面でConsumerKeyとConsumerSecretを確認できます

![apps.twitter.com_app_15226818_keys(Laptop with MDPI screen).png](https://qiita-image-store.s3.amazonaws.com/0/178077/88103d9e-ee98-e0dc-b284-3f9727d25980.png)

## :pencil2: リクエストトークンを取得して連携画面のURLを生成する
- 連携画面のURLは`https://api.twitter.com/oauth/authenticate?oauth_token=XXX`のようになっています
- この`XXX`に入るのがリクエストトークンと呼ばれるものです
- リクエストトークンは [POST oauth/request_token](https://developer.twitter.com/en/docs/basics/authentication/api-reference/request_token) より取得できます
- 先ほど設定したCallbackURLsのうち、どれかを`oauth_callback`としてパラメーターに渡す必要があります
- OAuthセッションを1から作るのは面倒ですが、[requests_oauthlib](https://github.com/requests/requests-oauthlib)というライブラリを使うことで簡単に実装できます

```py
    from requests_oauthlib import OAuth1Session
    from urllib.parse import parse_qsl

    consumer_key = "Consumer Key を入力"
    consumer_secret = "Consumer Secret を入力"

    # Twitter Application Management で設定したコールバックURLsのどれか
    oauth_callback = "CallbackURLsのどれか"

    twitter = OAuth1Session(consumer_key, consumer_secret)

    response = twitter.post(
        request_token_url,
        params={'oauth_callback': oauth_callback}
    )

    # responseからリクエストトークンを取り出す
    request_token = dict(parse_qsl(response.content.decode("utf-8")))

    # リクエストトークンから連携画面のURLを生成
    authenticate_url = "https://api.twitter.com/oauth/authenticate"
    authenticate_endpoint = '%s?oauth_token=%s' \
        % (authenticate_url, request_token['oauth_token'])

    print(authenticate_endpoint)

```

## :pencil2: 連携後のパラメーターを使ってアクセストークンを生成する
- 先ほど生成したURLにアクセスして「連携アプリを認証」すると、指定したcallbackURLに遷移します。この時、URLには`?oauth_token=YYY&oauth_verifier=ZZZ`というパラメーターがついているはずです
- この2種類のパラメーターを使うことで、アクセストークンを生成し、連携したユーザーとしてTwitterのAPIを叩くことができるようになります
- アクセストークンは [POST oauth/access_token](https://developer.twitter.com/en/docs/basics/authentication/api-reference/access_token) より取得することができます
- アクセストークンには`oauth_token`と`oauth_token_secret`の2つが含まれており、両方とも必要です

```py

    from requests_oauthlib import OAuth1Session
    from urllib.parse import parse_qsl

    consumer_key = "Consumer Key を入力"
    consumer_secret = "Consumer Secret を入力"

    oauth_token = "callbackURLのパラメーター(YYY)"
    oauth_verifier = "callbackURLのパラメーター(ZZZ)"

    twitter = OAuth1Session(
        consumer_key,
        consumer_secret,
        oauth_token,
        oauth_verifier,
    )

    response = twitter.post(
        access_token_url,
        params={'oauth_verifier': oauth_verifier}
    )

    # responseからアクセストークンを取り出す
    access_token = dict(parse_qsl(response.content.decode("utf-8")))

    print(access_token)

```

## :pencil2: アクセストークンを使ってユーザーのツイートを取得する
- 上記で取得したアクセストークンを使ってユーザーのツイートを取得する例です
- [GET statuses/user_timeline](https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-user_timeline.html) より取得することができます

```py

    import json

    consumer_key = "Consumer Key を入力"
    consumer_secret = "Consumer Secret を入力"

    access_token = {
        'oauth_token': "アクセストークンの中のoauth_token",
        'oauth_token_secret': "アクセストークンの中のoauth_token_secret",
    }

    params = {
        'user_id': "アクセストークンの中のuser_id",
        'exclude_replies': True,
        'include_rts': json.get('include_rts', False),
        'count': 20,
        'trim_user': False,
        'tweet_mode': 'extended',    # full_textを取得するために必要
    }

    twitter = OAuth1Session(
        consumer_key,
        consumer_secret,
        access_token['oauth_token'],
        access_token['oauth_token_secret'],
    )

    response = twitter.get(user_timeline_url, params=params)
    results = json.loads(response.text)

    print(results)

```

# :pencil: flaskでAPIとして実装した例


```python:index.py


import os
import json
from urllib.parse import parse_qsl

from flask import Flask, jsonify, request
from requests_oauthlib import OAuth1Session

app = Flask(__name__)


consumer_key = "Consumer Key を入力"
consumer_secret = "Consumer Secret を入力"


base_url = 'https://api.twitter.com/'

request_token_url = base_url + 'oauth/request_token'
authenticate_url = base_url + 'oauth/authenticate'
access_token_url = base_url + 'oauth/access_token'

base_json_url = 'https://api.twitter.com/1.1/%s.json'
user_timeline_url = base_json_url % ('statuses/user_timeline')


# 認証画面（「このアプリと連携しますか？」の画面）のURLを返すAPI
@app.route('/twitter/request_token', methods=['GET'])
def get_twitter_request_token():

    # Twitter Application Management で設定したコールバックURLsのどれか
    oauth_callback = request.args.get('oauth_callback')

    twitter = OAuth1Session(consumer_key, consumer_secret)

    response = twitter.post(
        request_token_url,
        params={'oauth_callback': oauth_callback}
    )

    request_token = dict(parse_qsl(response.content.decode("utf-8")))

    # リクエストトークンから認証画面のURLを生成
    authenticate_endpoint = '%s?oauth_token=%s' \
        % (authenticate_url, request_token['oauth_token'])

    request_token.update({'authenticate_endpoint': authenticate_endpoint})

    return jsonify(request_token)


# アクセストークン（連携したユーザーとしてTwitterのAPIを叩くためのトークン）を返すAPI
@app.route('/twitter/access_token', methods=['GET'])
def get_twitter_access_token():

    oauth_token = request.args.get('oauth_token')
    oauth_verifier = request.args.get('oauth_verifier')

    twitter = OAuth1Session(
        consumer_key,
        consumer_secret,
        oauth_token,
        oauth_verifier,
    )

    response = twitter.post(
        access_token_url,
        params={'oauth_verifier': oauth_verifier}
    )

    access_token = dict(parse_qsl(response.content.decode("utf-8")))

    return jsonify(access_token)



@app.route('/twitter/user_timeline', methods=['GET'])
def get_twitter_user_timeline():

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

    params = {
        'user_id': request.args.get('user_id'),
        'exclude_replies': True,
        'include_rts': json.get('include_rts', False),
        'count': 20,
        'trim_user': False,
        'tweet_mode': 'extended',    # full_textを取得するために必要
    }

    twitter = OAuth1Session(
        consumer_key,
        consumer_secret,
        access_token['oauth_token'],
        access_token['oauth_token_secret'],
    )

    response = twitter.get(user_timeline_url, params=params)
    results = json.loads(response.text)

    return jsonify(results)

if __name__ == "__main__":
    port = os.environ.get('PORT', 3333)
    app.run(
        host='0.0.0.0',
        port=port,
    )

```

# :star: 感想
- OAuth認証の流れやお作法的なものがあんまり分かっていなかったので思ったより苦戦しました
- CallbackURLについてはフロントの話なので省きましたが、前方一致ではなく完全一致なので`https:///example.com/user/001/twitter`のような構成だったので少し工夫が必要でした
- Facebook/Instagramの認証もPython+React.jsで実装したので、そのうち記事を書こうと思います


# :star2: 追記
- フロントでJavaScriptが使える場合、アクセストークンの取得はfirebaseのAutheticationを使うとシンプルに実装できるのでおすすめです
- [Firebase： JavaScript で Twitter を使用して認証する](https://firebase.google.com/docs/auth/web/twitter-login?hl=ja)