【Python】OAuth2.0認証を利用してTwitter APIと連携し、認証されたTwitter IDを得る方法
はじめてZennを利用します。どんな感じなのかなと思い、試しに書いている感じですので、何卒宜しくお願い致します。皆さんもそうだと思いますが、学びならやっておりますので、間違った情報もあるかと思います。その際は、遠慮なくご指摘していただけますと幸いです。
はじめに
弊社でWeb3関連のプロダクトを開発するにあたり、ウォレットアドレス(ユーザログインで利用)とTwitterのアカウントを連携させる必要性がでてきました。海外のWeb3プロダクトを見ていても、OAuth2.0が主流のようです。
Twitterは昔Pythonで遊んでた頃にAPI使っていたものの、今回OAuth2.0は初めてです。実際にどうやって連携させているのかわかりませんでしたが、散らばっている知識を組み合わせながら試してみました。意外と詳しい記事がなくて困りました!
今回はAccess Tokenを取得して、アクセス許可したTwitterのIDを得るところまで実装していきたいと思います。
前提知識
- Twitter API
- PKCE(Proof Key Code Exchange)
- Python
- OAuth2.0の仕組み
- (Postman)
実装
全体の流れ
- Twitter APIを利用するためにDeveloper Portalで諸設定を行う
- Authorization Requestを行う
- Access Tokenを取得する
- Access Tokenを利用してTwitter IDを得る
Client IDとClient Secretを公式から取得する
Twitter API周りの設定方法など詳細は公式ドキュメントや他の記事を参照してください。
ここは重要なところだけ詳しく説明していきます。まずはDeveloper Portalのダッシュボードに行きましょう。
Redirect URI
はOAuth2.0認証後にリダイレクトするURLを入れてください。このRedirect URI
は後ほどAuthorization Requestを行う際に利用します。
次にClient IDとClient Secretを取得しましょう。
TWITTER_CLIENT_ID = os.getenv("TWITTER_CLIENT_ID")
TWITTER_CLIENT_SECRET = os.getenv("TWITTER_CLIENT_SECRET")
ハードコーディングでも良いですが、念のためこのような形で環境変数から読みとることにします。
Authorization RequestのためのURLを生成する
下準備は整いましたので、早速コーディングしていきます。下記はAuthorization RequestのためのURLを生成する完成コードと出力結果です。
import base64
import dotenv
import hashlib
import json
import os
import urllib.parse as parse
dotenv.load_dotenv()
TWITTER_CLIENT_ID = os.getenv("TWITTER_CLIENT_ID")
TOKEN_URL = "https://api.twitter.com/2/oauth2/token"
AUTH_URL = "https://twitter.com/i/oauth2/authorize"
REDIRECT_URI = "http://localhost:3000/twitter/redirect"
SCOPE = ["users.read", "tweet.read"]
state = hashlib.sha256(os.urandom(32)).hexdigest()
code_verifier = hashlib.sha256(os.urandom(128)).hexdigest()
code_challenge_sha256 = hashlib.sha256(code_verifier.encode()).digest()
code_challenge = base64.urlsafe_b64encode(code_challenge_sha256).decode().rstrip("=")
query_parameter = parse.urlencode({
'client_id': TWITTER_CLIENT_ID,
'redirect_uri': REDIRECT_URI,
'state': state,
'scope': ' '.join(SCOPE),
'response_type': 'code',
'code_challenge': code_challenge,
'code_challenge_method': 'S256',
})
request_url = f'{AUTH_URL}?{query_parameter}'
{
'request_url': 'https://twitter.com/i/oauth2/authorize?client_id=N04tOTZUbjIwb0VIMDBMVlcxN1I6MTpjaQ&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fredirect&state=b2262218cd73792b71b862397afa0a0c1c3077f17e9947890701a7b1d9fe32d6&scope=users.read+tweet.read+follows.read&response_type=code&code_challenge=JOgyZWsvZLPy_u18dr4d4nBYH1pKzPJFtac_afNSrHY&code_challenge_method=S256',
'state': 'b2262218cd73792b71b862397afa0a0c1c3077f17e9947890701a7b1d9fe32d6',
'code_verifier': '15d1dc906b3739ecaca6e721cdf0cf72845d1714a173eafd678812d97f295f15'
}
query_parameter
を見ていきます。
-
client_id
Twitter APIのDeveloper Portalで取得したClient ID -
redirect_uri
Developer Portalで設定したRedirect URI -
state
CSRF対策用の任意値 -
scope
アクセス許可する権限範囲。下記のDocsにはtweet.write
やlike.write
などについて詳細が書かれています。
https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code -
state
CSRF対策用の任意値 -
response_type
code
固定 -
code_challenge
code_challenge_method
で設定したアルゴリズムで算出した値。 -
code_challenge_method
plane
もしくはS256
。plane
の場合は、code_challenge
とcode_verifier
は同じ値にする。S256
の場合はcode_challenge
の値をSHA256でハッシュ化してBASE64URL Encodingした値にする。今回はS256
で試す。
こちらの記事を参照しました。
アクセス許可してCodeを得る
https://twitter.com/i/oauth2/authorize?client_id=N04tOTZUbjIwb0VIMDBMVlcxN1I6MTpjaQ&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fredirect&state=b2262218cd73792b71b862397afa0a0c1c3077f17e9947890701a7b1d9fe32d6&scope=users.read+tweet.read+follows.read&response_type=code&code_challenge=JOgyZWsvZLPy_u18dr4d4nBYH1pKzPJFtac_afNSrHY&code_challenge_method=S256
先ほどのoutput
のrequest_url
のリンクで、自身が所有するTwitterのアカウントにアクセス許可をします。なお、アクセス許可してから30秒以内でAccess Tokenを取得する必要があります(詳しくは次)。 思ったよりも早いです!
アクセス許可をすると下記のresponse
にリダイレクトされます。
http://localhost:3000/twitter/redirect?state=4b1ce2276ac4609a545401729073c007e2def72b0acba4ee22d02d62be7361bf&code=RlRRa0hHX3dNZzVYV1VFdkZjMU1lX2wxeVQzUDduVi15bE01NFdBVF95WVFVOjE2NzQ4MjgzOTU0MDk6MTowOmFjOjE
-
http://localhost:3000/twitter/redirect
Developer Portalで設定したRedirect URIです。 -
state
フロント側は完全無視してますのでこちらは利用しません。
4b1ce2276ac4609a545401729073c007e2def72b0acba4ee22d02d62be7361bf
-
code
Access Tokenを取得するために必要なCodeになります。RlRRa0hHX3dNZzVYV1VFdkZjMU1lX2wxeVQzUDduVi15bE01NFdBVF95WVFVOjE2NzQ4MjgzOTU0MDk6MTowOmFjOjE
エラー: Something went wrong
最初の関門かも知れません。request_url
のリンクを開くとこのようなエラーが表示されることもあります。
Something went wrong
You weren't able to give access to the App. Go back and
try logging in again.
たとえば、request_url
が設定したURIと違ったURIを入れてしまった場合、うまくAuthorization Requestを飛ばすことができません。http://localhost:3000/twitter/redirect
と書いたつもりが、http://localhost:3000/redirect
になっていたり。欠損している箇所を探してみましょう。
Access Tokenを取得する
下記はAccess Tokenを取得するための完成コードと出力結果です。
import hashlib
import os
import json
import urllib.parse as parse
import urllib.request as req
import base64
import dotenv
TWITTER_CLIENT_SECRET = os.getenv("TWITTER_CLIENT_SECRET")
# 生成されたCodeと生成したCode Verifierに置換する
code = "code"
code_verifier = "code_verifier"
data = parse.urlencode({
'client_id': TWITTER_CLIENT_ID,
'client_secret': TWITTER_CLIENT_SECRET,
'redirect_uri': REDIRECT_URI,
'grant_type': 'authorization_code',
'code': code,
'code_verifier': code_verifier
}).encode('utf-8')
# Basic認証でURL先からレスポンスを得る
authorization = base64.b64encode(
f'{TWITTER_CLIENT_ID}:{TWITTER_CLIENT_SECRET}'.encode('utf-8')
).decode('utf-8')
request = req.Request(
TOKEN_URL,
headers={
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': f'Basic {authorization}'
}
)
with req.urlopen(request, data=data) as token_res:
res = token_res.read()
print(res)
{
'token_type': 'bearer',
'expires_in': 7200,
'access_token':
'M0UxenAyN0hveVBpMVFlcTJ3SlVHLVdPSE5kYjdXWnp4X1pFa0ZZSi1ibDQzOjE2NzQ4MzUwNTIyMDM6MTowOmF0OjE',
'scope': 'users.read tweet.read'
}
output
を見てください。access_token
が発行されたアクセストークンです。expires_in`はアクセストークンの有効期限で、7200秒なので2時間有効となっています。これでTwitter APIを利用することが可能になります。
get_access_token.py
をみて見ましょう。ここで重要なのは、code
とcode_verifier
です。「アクセス許可してCodeを得る」にてクエリパラメータで返ってきたcode
と「Authorization RequestのためのURLを生成する」で生成したcode_verifier
を入力してください。30秒以内に行う必要があります。
また、フロントエンド側の処理を行っている人は、code
とcode_verifier
がしっかり紐づいているかに注意する必要があります。識別にstate
が役立つかもしれません。よくcode
は最新だけれど、code_verifier
は違う値になっていたということがあります。
さて、access_token
を発行することができましたので今度は利用してみましょう。
Access Tokenを利用してTwitter IDを得る
ラストスパートですが、ここは比較的簡単です。サンプルさえあれば、他にも応用ができると思うので、色々と試してみてください。
下記が完成コードです。
def get_twitter_id(access_token):
url = 'https://api.twitter.com/2/users/me'
# Paramsの使い方も参考程度に
params = {
'user.fields': 'description'
}
# Access Tokenをセットする
headers = {
'Authorization': f'Bearer {access_token}'
}
res = requests.get(url, params=params, headers=headers)
return eval(res.text)
get_twitter_id("Access Token")
{
'data': {
'description': 'description',
'id': 'id',
'name': 'name',
'username': 'username'
}
}
get_twitter_id.py
を見てください。get_twitter_id("Access Token")
には当然先ほど発行したAccess Tokenが入ります。書かなくても分かると思いますが念のため書いておきます。
get_twitter_id("M0UxenAyN0hveVBpMVFlcTJ3SlVHLVdPSE5kYjdXWnp4X1pFa0ZZSi1ibDQzOjE2NzQ4MzUwNTIyMDM6MTowOmF0OjE")
次にoutput
を見てください。username
がTwitter IDということになりますので、今回の目標は達成です。params
の設定は、例えば、user.fields
は上記では'description'
しか利用していませんが、他にもcreated_at
やprofile_image_url
などがあります。
まずはどのような挙動になるのかをPostmanで試してみることを推奨します。
エラーが発生した場合の対処法
HTTPError: HTTP Error 400: Bad Request
アクセス許可してから30秒以内にAccess Tokenを得る
何度も書きましたが、30秒の制限があります。これを知らなかったときは本当にエラーの原因が何なのかわからなかったので、しっかり30秒以内にできているか確認すると良いでしょう。
Code Challenge MethodをPlaneにして試す
上記では、Code Challenge MethodをS256
で実装しています。
もしかするとcode_challenge
とcode_verifier
が合っていない可能性がありますので、生成がうまくいかない場合は、code_challenge_method
をplane
にして、code_challenge
とcode_verifier
の値を一致させてみてください。
Postmanで挙動を見直す
いきなりコーディングすると訳が分からないときがあります。どのパラメータがどのように機能するのか、Twitter側が用意してくれているPostmanを利用することによって、動きをシミュレーションすることができます。
もちろん、OAuth2.0のアクセストークンを取得するところから確認することができます。
その他
下記のようなエラーが出た場合は、こちらの記事が参考になります。
{
"error": "unauthorized_client",
"error_description": "Missing valid authorization header"
}
{
"error": "invalid_request",
"error_description": "Missing required parameter [client_id]."
}
{
"error": "unauthorized_client",
"error_description": "Missing valid authorization header"
}
まとめ
だいたい、調べているときって「まとめ」見ませんよね。私の感想を適当においておこうと思います。
開発しながらアウトプットを出しているエンジニアさんって本当にすごいなと思います。私はこれを作成するのに、3時間30分もかかりました…。
普段はNotionで開発ノートと呼んでいるメモを残しているのですが、見直すことはほとんどありません。見直すのは最初の環境構築ぐらいですかね。それでも、参考になった記事をメモしたり、気づきをメモしたりは重要だと思いますし、覚えるための一方にはなると思っています。
さすがに時間がかかりすぎなので、手の込んだものは書けないと思いますが、何かを一から復習したりするときにはZennを書きながら(アウトプットしながら)学習していくのも良さそうです。
本件とは関係のない書き手の拙い感想ですが、色々と発見ができて良かったと思います。間違っていることもございましょうがご参考になれば幸いです。
ChatGPTに負けないようにたくさん学ばねば!
Discussion