Flaskを使用したTwitterログイン連携用API作成 メモ

* Flask を使用してTwitterログイン連携するためのAPI作成方法に関する個人用メモ。
  * [Google連携用API](https://qiita.com/KWS_0901/items/95ce6e28e44f14c38545)を試した時と同様に、Docker起動できる形で[過去に作成したTwitterログイン検証コード](https://qiita.com/KWS_0901/items/ea46b3b66321b793b495)を部分的にAPI化した。


## 作成するAPI

### 認可リクエスト作成API

* Twitter認可エンドポイントへアクセスするためのURLをパラメータをつけて生成・返却する。

  * `oauth_token(request_token)`を取得し、認可リクエストURLにクエリパラメータとして設定する。

  * `api_key`など固定の属性値は環境変数から取得する。

  * リクエスト例

    ```http
    POST /api/twitter/auth_request/create HTTP/1.1
    Host: localhost:5000
    ```

  * レスポンス例

    ```json
    {
        "authorization_request_url": "https://api.twitter.com/oauth/authenticate?oauth_token=1234ABCD"
    }
    ```


### トークンリクエスト+ユーザー情報取得API

* Twitterからの認可レスポンスとして受け取った`oauth_verifier`と`oauth_token`を指定して、トークンリクエスト＋ユーザー情報取得を行う。

  * リクエスト例

    ```http
    POST /api/twitter/auth_request/complete HTTP/1.1
    Host: localhost:5000
    Content-Type: application/json
    Content-Length: 111
    
    {
        "oauth_verifier":"AAABBBCCC",
        "oauth_token":"1234ABCD"
    }
    ```

  * レスポンス例

    ```json
    [
        {
            "contributors_enabled": ...,
            "created_at": ...,
            "default_profile": ...,
            "default_profile_image": ...,
            "description": ...,
            "entities": {
               ...
                }
            },
           ...
        }
    ]
    ```

    

  

## プロジェクト構成

```
facebook_oidc
└─ docker-compose.yml
└─ twitter.env
│
└─ be
   └─ Dockerfile
   └─ requirements.txt
   │
   └─app
      └─ app.py
      │
      └─ api
          └─ __init__.py
          │
          └─views
             └─  twitter.py
```

## 実装

* `docker-compose.yml`

  ※起動時に環境変数ファイル`twitter.env`を読み込む。

  ```yaml
  version: "3"
  services:
    be:
      container_name: be
      build: ./be
      env_file: twitter.env
      volumes:
        - ./be/app:/app
      ports:
        - "5000:5000"
      command: flask run --host 0.0.0.0 --port 5000
      tty: true
  ```

* `twitter.env`

  * 環境変数ファイル。アプリケーション登録時に発行・設定した値を指定する。

  ```env
  TWITTER_API_KEY=YOUR_API_KEY
  TWITTER_API_SECRET=YOUR_API_SECRET
  TWITTER_AUTHORIZATION_ENDPOINT=https://api.twitter.com/oauth/authenticate
  TWITTER_REQUEST_TOKEN_ENDPOINT=https://api.twitter.com/oauth/request_token
  TWITTER_TOKEN_ENDPOINT=https://api.twitter.com/oauth/access_token
  TWITTER_USERINFO_ENDPOINT=https://api.twitter.com/1.1/users/lookup.json
  ```

* `be/requirements.txt`

  * Pythonライブラリ一式

  ```
  Flask
  Flask-Cors
  requests_oauthlib
  ```

* `be/Dockerfile`

  ```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`

  ```python
  from api import app
  
  if __name__ == '__main__':
      app.run()
  ```

* `be/api/__init__.py`

  ```
  from flask import Flask
  from .views.twitter import twitter_router
  from flask_cors import CORS
  
  
  def create_app():
  
      app = Flask(__name__)
  
      CORS(app, supports_credentials=True)
  
      app.register_blueprint(twitter, url_prefix='/api')
  
      return app
  
  
  app = create_app()
  
  ```

* `be/api/views/twitter.py`

  * コントローラー

  * ※エラーハンドリング皆無

    ```python
    import hashlib
    from flask import Flask, Blueprint, request, jsonify
    from requests_oauthlib import OAuth1Session
    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
    twitter_router = Blueprint('twitter_router', __name__)
    
    # Client Param
    api_key = os.getenv('TWITTER_API_KEY')
    api_secret = os.getenv('TWITTER_API_SECRET')
    
    # Twitter Endpoint
    authorization_endpoint = os.getenv('TWITTER_AUTHORIZATION_ENDPOINT')
    request_token_endpoint = os.getenv('TWITTER_REQUEST_TOKEN_ENDPOINT')
    token_endpoint = os.getenv('TWITTER_TOKEN_ENDPOINT')
    userinfo_endpoint = os.getenv('TWITTER_USERINFO_ENDPOINT')
    
    app = Flask(__name__)
    
    
    # Create Authorization Request Endpoint
    @twitter_router.route("/twitter/auth_request/create",  methods=['POST'])
    def create():
        # Obtaining a request token
        twitter = OAuth1Session(api_key, api_secret)
        oauth_callback = request.args.get('oauth_callback')
        res = twitter.post(request_token_endpoint, params={
            'oauth_callback': oauth_callback})
        request_token = dict(parse.parse_qsl(res.content.decode("utf-8")))
        oauth_token = request_token['oauth_token']
    
        # Create Authz Request URL
        auth_request_url = authorization_endpoint+'?{}'.format(parse.urlencode({
            'oauth_token': oauth_token
        }))
        res_body = {
            "authorization_request_url": auth_request_url
        }
        return json.loads(json.dumps(res_body))
    
    
    # Token Request And Get User Info Endpoint
    @twitter_router.route("/twitter/auth_request/complete", methods=['POST'])
    def complete():
    
        # Parse Req Body
        jsonData = json.dumps(request.json)
        req_body = json.loads(jsonData)
        oauth_verifier = req_body['oauth_verifier']
        oauth_token = req_body['oauth_token']
    
        # Token Request
        twitter = OAuth1Session(
            api_key,
            api_secret,
            oauth_token
        )
        res = twitter.post(
            token_endpoint,
            params={'oauth_verifier': oauth_verifier}
        )
        access_token = dict(parse.parse_qsl(res.content.decode("utf-8")))
    
        # Get User Info
        # https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/follow-search-get-users/api-reference/get-users-lookup
        params = {
            'user_id': access_token['user_id']
        }
    
        twitter = OAuth1Session(
            api_key,
            api_secret,
            access_token['oauth_token'],
            access_token['oauth_token_secret'],
        )
    
        response = twitter.get(userinfo_endpoint, params=params)
        results = json.loads(response.text)
    
        return jsonify(results)
    ```

    

## 起動

```shell
docker-compose up -d
```



## 参考情報

* [3-legged authorization](https://developer.twitter.com/ja/docs/basics/authentication/overview/3-legged-oauth)
* [Log in with Twitter](https://developer.twitter.com/en/docs/authentication/guides/log-in-with-twitter)
* [[Python\] OAuth認証でTwitter連携/ログインを実装する](https://qiita.com/mikan3rd/items/686e4978f9e1111628e9)