見出し画像

#85 OAuth

 MetaのThreadsが話題です。試してみましたか?ほとんどTwitterのような使い勝手です。Instagramのアカウントがあれば会員登録も不要で始められ、友だちもそのまま引き継がれるようです。セキュリティに問題があるという噂もありますが…まだよくわかりません。エンジニアとしては、APIがいつ公開されるかが気になるところですね。

 TwitterやFacebookなどのサービスをシステムに組み込むのはよくあることです。会員登録なしでログインできたり、ブログの内容を自動で投稿したりなどできると便利です。その際、すべての情報や権限をシステムに許可してしまうと、ユーザーの意図しない投稿を勝手にされてしまったり、機微な情報が流出してしまうかもしれません。そこで必要最小限の権限のみを与えてサービスを連携するための仕組みが必要になってきます。それがOAuthです。各種サービスとAPI連携する際にはよく使われます。

 Twitterで実際にやってみます!PythonからOAuthで認証して、Tweetするところまでやります。


Twitter Developer登録

無料プランで登録します。APIの利用目的など聞かれますが、適当に書いときます。「User authentication settings」を設定すると、ClientIDとClient Secretが発行されるので、控えてください。


認証

 OAuthはHTTP通信を使った認証方法です。Twitterの認証画面をブラウザで開く必要があるので、まず認証用のURLを生成します。TwitterAPIでは、ハッシュを利用したPKCEという認証方法が使えます。

# auth.py
from urllib import request, parse, error
from hashlib import sha256
import base64
import random

def pkce_hash(code):
    hash = sha256(code.encode()).digest()
    b64 = base64.b64encode(hash).decode()
    challenge = b64.replace('=', '').replace("+", "-").replace("/", "_")
    return challenge

def auth_url(verifier):
    params = {
        "response_type": "code",
        "client_id": <CLIENT_ID>,
        "redirect_uri": "http://localhost",
        "scope": "tweet.read tweet.write users.read offline.access",
        "state": random.randbytes(8).hex(),
        "code_challenge": pkce_hash(verifier),
        "code_challenge_method": "S256"
    }

    query = parse.urlencode(params)
    url = "https://twitter.com/i/oauth2/authorize" + "?" + query
    return url

if __name__ == "__main__":
    verifier = random.randbytes(8).hex()
    url = auth_url(verifier)
    print(verifier)
    print(url)
$ python3 auth.py
<verifier>
https://twitter.com/i/oauth2/authorize?response_type=code&client_id=dVhWNUpxVkQ1RFJiRXNaSkFtMF86MTpjaQ&redirect_uri=http%3A%2F%2Flocalhost&scope=tweet.read+tweet.write+users.read+offline.access&state=22e18948b4da56e9&code_challenge=vgtTgSj7iVWrcbYJ8Xc3VV6Bm-aqwHOm5d9Fa-_m1MQ&code_challenge_method=S256

「<verifier>」は、PKCEで使う認証鍵です。後で使うので、保存しておいてください。「scope」に許可を求める権限を指定しています。生成したURLにアクセスすると、Twitterの認証画面に遷移します。

「Authorize app」をクリックすると、Redirect URIに設定した場所に遷移します。Getパラメータにcodeという名前でAuthorization codeが設定されているので、次にこれを使ってアクセストークンを発行します。


アクセストークン発行

 アクセストークンは、実際に投稿を行ったり、情報を取得したりする際に必要になります。Authorization codeから、アクセストークンと、アクセストークンを更新するためのリフレッシュトークンが発行できます。Authorization codeは数秒で失効してしまうようなので、素早く操作してください!

# access_token.py
from urllib import request, parse, error
import base64

def request_token(code, verifier):
    header = {
        "Authorization": "Basic " + base64.b64encode(bytes(<CLIENT_ID> + ":" + <CLIENT_SECRET>, 'utf-8')).decode('utf-8'),
        "Content-Type": "application/x-www-form-urlencoded"
    }

    body = {
        "grant_type": "authorization_code",
        "code": code,
        "client_id": <CLIENT_ID>,
        "redirect_uri": "http://localhost",
        "code_verifier": verifier
    }

    req = request.Request("https://api.twitter.com/2/oauth2/token", data=parse.urlencode(body).encode(), headers=header)

    try:
        with request.urlopen(req) as res:
            body = res.read()
            if not body:
                body = b''
            return body
    except error.URLError as e:
        print("URL Error: " + e.reason)
        return b''
    except error.HTTPError as e:
        print("HTTP Error: " + str(e.code))
        print(e)
        return b''
    
if __name__ == "__main__":
    verifier = "<verifier>"
    code = "V09yVm81bWQyZUw0aGlSWXhvNEszaThpVkhfREZ0NW44TFNWZHMwQnRsdlE1OjE2ODg5MTI3Nzk2OTE6MToxOmFjOjE"
    res = request_token(code, verifier)
    print(res)
$ python3 access_token.py
{
    "token_type": "bearer",
    "expires_in": 7200,
    "access_token": "<access_token>",
    "scope": "tweet.write users.read tweet.read offline.access",
    "refresh_token": "<refresh_token>"
}


投稿

 先ほど取得した、アクセストークンを使って投稿を行います。2時間ほどで失効するので、その後はリフレッシュトークンを使って再発行を行う必要があります。

from urllib import request, parse, error
import json

def tweet(access_token, text):
    header = {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type': 'application/json'
    }
    body = {
        'text': text
    }
    req = request.Request("https://api.twitter.com/2/tweets", data=json.dumps(body).encode(), headers=header)
    try:
        with request.urlopen(req) as res:
            body = res.read()
            if not body:
                body = b''
            return body
    except error.URLError as e:
        print("URL Error: " + e.reason)
        return b''
    except error.HTTPError as e:
        print("HTTP Error: " + str(e.code))
        print(e)
        return b''
    
if __name__ == "__main__":
    token = "<access_token>"
    text = "test"
    res = tweet(token, text)
    print(res)
$ python3 tweet.py
{"data":{"edit_history_tweet_ids":["1678051052460048384"],"id":"1678051052460048384","text":"test"}}

Twitterを開いてみると、

勝手にツイートされました!自分のアカウント以外でも、OAuth認証を行ってアクセストークンを取得できれば、投稿ができます。Facebookでは、スパム投稿が問題になってこのあたりの仕様が少し厳しくなっていますが、似たような手順で投稿が可能です。


まとめ

 OAuthを利用して、サービスの連携ができました。便利な反面、投稿したり情報を閲覧したりする権限を与えることになるので、不用意に認証しないよう注意しなければいけません。もちろん、サービスを提供する側としても最大限の注意を払ってシステムを構築するよう心がけます。

EOF


この記事が気に入ったらサポートをしてみませんか?