BOXを使ってOAuth2.0を理解する


BOXを使ってOAuth2.0を理解する

前回学んだOAuth2.0認証フローをpythonを使ってやってみる
https://note.com/shirotabistudy/n/n4837786f8e53
ただし今回はBOX
 APIを手軽に利用できるライブラリーboxsdkは使わない。今回はAPIの使い方、認証フローについて学ぶため愚直にやってみる。

フローの確認


手順

1. BOXのアカウントを作る

2. BOXの開発者コンソールの利用申請

3. BOXの通常のページに以下のようなものが出るのでアクセスする

ClientIDとClientSecret,リダイレクトURIを取得

 4. アプリを新規作成する

 5. アプリの種別を選択(カスタムアプリを選択)

 6. 指示に従って必要事項を入力していく

 7. 構成タブでも必要事項を記入

 8.構成タブのページの下の方にClientIDとClientSecretがあるのでどっかにコピーする

9.リダイレクトURIを設定する


 10. リダイレクトURIにアクセスする準備 リクエスト方法の確認


公式のページでアクセス方法を確認します。
https://ja.developer.box.com/reference/get-authorize/

他重要な要素

クエリパラメータ
client_id string クエリ内必須

ly1nj6n11vionaie65emwzk575hnnmrk

ユーザーの認証をリクエストしているアプリケーションのクライアントID。アプリケーションのクライアントIDを取得するには、Box開発者コンソールにログインして、該当するアプリケーションの [アプリケーションの編集] リンクをクリックします。[構成] ページの [OAuth2パラメータ] セクションで、client_idというラベルの付いた項目を探します。その項目のテキストが、アプリケーションのクライアントIDです。

redirect_uri string / url クエリ内省略可能

例http://example.com/auth/callback

ユーザーがアプリケーションの権限を許可または拒否した後にブラウザがリダイレクトされるURI。このURIは、アプリケーションの構成に含まれるリダイレクトURIのいずれかと一致します。このURIは有効なHTTPS URIでなければならず、OAuth 2.0フローの次の手順を完了するには、リダイレクトを処理できる状態になっている必要があります。このパラメータは省略可能ですが、開発者コンソールでアプリケーション用にリダイレクトURIを複数設定した場合は、承認URLに含める必要があります。パラメータが指定されていないと、ユーザーがアプリケーションにアクセス権限を付与した後にredirect_uri_missingエラーが発生します。

response_type string / token クエリ内必須

code
受信するレスポンスのタイプ。
次の値に固定: code

11. Python を使って上記の情報をもとに認証ページにアクセスする

import webbrowser
import configparser
from urllib.parse import urlencode

# 設定ファイルの読み込み
config = configparser.ConfigParser()
config.read('config.ini')

# OAuthプロバイダーの認証ページのURL
authorization_base_url = 'https://account.box.com/api/oauth2/authorize'

# アプリケーションのクライアントID(Box開発者コンソールから取得)
client_id = config['API_KEY']['CLIENT_ID']

# ステップ1で設定したリダイレクトURI
redirect_uri = 'http://127.0.0.1:8080/'

# 認証URLの生成
params = {
    'response_type': 'code',
    'client_id': client_id,
    'redirect_uri': redirect_uri,
}
# パラメータをURLに組み込む
auth_url = f"{authorization_base_url}?{urlencode(params)}"

# ユーザーにブラウザで認証URLを開くよう指示
print("Please go to the following URL and authorize the app:")
print(auth_url)
# ブラウザで自動的にURLを開く
webbrowser.open(auth_url)

上記を実行すると

ブラウザで上記ページに移動します。
アクセスを許可すればリダイレクトのページのURLに認証コードが付与されます。

code=*******の部分が認証コードです。
これをメモって次のステップに写ります。

12. アクセストークンをリクエスト

パラメータはどんなものを送ればいいのでしょう?
ということで続けて読んでいくと

リクエスト本文

なんか右上に必須と書いてあるし、とりあえずこれは必要そう。でも書いてあることよくわからん。教えてClaude。

以下Claudeによる説明

grant_type は、OAuth 2.0のフローにおいて、アクセストークンを取得する際に使用される許可タイプを指定するパラメーターです。

OAuth 2.0では、リソースオーナー(ユーザー)がクライアントアプリケーション(サービス)に対して、自身のリソース(データ)へのアクセスを安全に許可するためのフローが定義されています。そのフローの中で、grant_typeは、どの種類の許可方式(grant type)を使ってアクセストークンを発行するかを示します。

grant_typeで指定可能な値とその意味は以下の通りです。

  1. authorization_code: 認証コード許可タイプ。ユーザーがクライアントアプリにアクセスを許可した際に発行される一時的な認証コードを使ってアクセストークンを取得する。

  2. refresh_token: リフレッシュトークン許可タイプ。有効期限の切れたアクセストークンを新しいアクセストークンと交換する際に使用する。

  3. client_credentials: クライアント資格情報許可タイプ。クライアントアプリケーション自身の資格情報(クライアントID/シークレット)を使ってアクセストークンを取得する。ユーザーコンテキストは不要。

  4. urn:ietf:params:oauth:grant-type:jwt-bearer: JWT Bearer許可タイプ。クライアントがJWT (JSON Web Token)を使ってリソースオーナーの代わりにアクセストークンを取得する。

  5. urn:ietf:params:oauth:grant-type:token-exchange: トークン交換許可タイプ。既存の tokensourceを使って新しいトークンを発行する。例えば、IdPのトークンをAPIゲートウェイのトークンに交換するなど。

要するに、grant_typeはリソースへのアクセスをどのような権限付与方式で許可するかを指定するパラメーターであり、OAuth 2.0の各フローの種類を決定づけるものです。アクセストークンの発行元となるリソースサーバーは、このパラメータに基づいて適切な許可チェックを行います。

とりあえず今回は認証コードを使ってアクセストークンをリクエストするのでgrant_typeはsuthorization_codeで指定するみたいです
他に必要なものがないかと調べると

説明文に"zuthorization_codeとの組み合わせで使うと書いてあるので、これらが少なくとも必要そうです。
 

13. アクセストークンをリクエストする Pythonコード


ドキュメントの”リクエスト本文”はdataというパラメータに渡します。

import requests
import configparser

# 設定ファイルの読み込み
config = configparser.ConfigParser()
config.read('config.ini')

# アプリケーションのクライアントIDとクライアントシークレット
client_id = config['API_KEY']['CLIENT_ID']
client_secret = config['API_KEY']['CLIENT_SECRET']
# アクセストークンを取得するためのエンドポイント
token_url = 'https://api.box.com/oauth2/token'

# POSTリクエストの本文に含めるデータ
data = {
    'grant_type': 'authorization_code',
    'code': authorization_code, # 1つ前で取得した認証コード
    'client_id': client_id,
    'client_secret': client_secret,
}

# アクセストークンを取得するためにPOSTリクエストを送信
response = requests.post(token_url, data=data)

# レスポンスからアクセストークンを取得
if response.status_code == 200:
    tokens = response.json()
    access_token = tokens['access_token']
    print("Access Token:", access_token)
    # 必要に応じてリフレッシュトークンも取得できます
    # refresh_token = tokens.get('refresh_token')
else:
    print("Error while retrieving the access token:", response.text)

これでアクセストークンが取得できました。

 

14. 一連のながれで

ただ、実際には認証コードをメモって、別途アクセストークンを取得するためのプログラムを動かすなんてことはないと思うので認証コードの取得からアクセストークン取得は一連の流れでないと不便です。

これを可能にするには認証コードを受け取るHTTPサーバを用意する必要があります。認証コードがリダイレクトURIにクエリパラメータとして付与されるためです。※htttps://127.0.0.1:8080/?code=*****の”?code=*****”がクエリパラメータ

PythonにはHTTPサーバを手軽に用意するライブラリーが用意されています。
以下、認証コードを受け取るためのHTTPサーバ用意のためのコード

# oauth2callback.py

import http.server
import threading
from urllib.parse import urlparse, parse_qs

class OAuth2CallbackHandler(http.server.SimpleHTTPRequestHandler):
    code = None  # インスタンス変数として認証コードを保持

    def do_GET(self) -> None:
        """GETリクエストを処理するメソッド"""
        query_components = parse_qs(urlparse(self.path).query)
        code = query_components.get('code', [None])[0]
            
        if code is not None:
            OAuth2CallbackHandler.code = code  # インスタンス変数に認証コードを保持
            print(f"Authorization code received: {self.code}")
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            self.wfile.write(b"Authentication successful. You can close this window.")



class MyHTTPServer():
    def __init__(self, port=8080) -> None:
        self.port = port
        self.handler_instance = None

    def _http_server(self):
        """OAuth2CallbackHandlerを使ってHTTPサーバーを起動するメソッド"""
        def handler(*args, **kwargs) -> OAuth2CallbackHandler:
            """OAuth2CallbackHandlerのインスタンスを生成する関数"""
            self.handler_instance = OAuth2CallbackHandler(*args, **kwargs)
            return self.handler_instance

        with http.server.HTTPServer(("", self.port), handler) as httpd:
            print(f"サーバーを起動しました。ポート番号: {self.port}")
            httpd.serve_forever()

    def start_server(self) -> None:
        """HTTPサーバーを別スレッドで起動するメソッド"""
        threading.Thread(target=self._http_server, daemon=True).start()

    def get_code(self) -> None | str:
        """認証コードを取得するメソッド"""
        return self.handler_instance.code  # リストの最初の要素を返す

手順11,13のコードを合体させて

# main.py

import webbrowser
import configparser
from urllib.parse import urlencode
from oauth2callback import MyHTTPServer
import requests

# サーバーを起動
my_http_server = MyHTTPServer()
my_http_server.start_server()

# 設定ファイルの読み込み
config = configparser.ConfigParser()
config.read('config.ini')

# OAuthプロバイダーの認証ページのURL
authorization_base_url = 'https://account.box.com/api/oauth2/authorize'

# アプリケーションのクライアントID(Box開発者コンソールから取得)
client_id = config['API_KEY']['CLIENT_ID']

# ステップ1で設定したリダイレクトURI
redirect_uri = 'http://127.0.0.1:8080/'

# 認証URLの生成
params = {
    'response_type': 'code',
    'client_id': client_id,
    'redirect_uri': redirect_uri,
}
# パラメータをURLに組み込む
auth_url = f"{authorization_base_url}?{urlencode(params)}"

# ユーザーにブラウザで認証URLを開くよう指示
print("Please go to the following URL and authorize the app:")
print(auth_url)

# ブラウザで自動的にURLを開く
webbrowser.open(auth_url)

# ブラウザでのユーザー操作を待つために簡単な入力を求める
input("After authorizing the app in the browser, press Enter to continue...")

# 取得した認証コード
authorization_code = my_http_server.get_code()

print(f"Authorization code: {authorization_code}")

# アプリケーションのクライアントIDとクライアントシークレット
client_id = config['API_KEY']['CLIENT_ID']
client_secret = config['API_KEY']['CLIENT_SECRET']

# アクセストークンを取得するためのエンドポイント
token_url = 'https://api.box.com/oauth2/token'

# POSTリクエストの本文に含めるデータ
data = {
    'grant_type': 'authorization_code',
    'code': authorization_code,
    'client_id': client_id,
    'client_secret': client_secret,
}

# アクセストークンを取得するためにPOSTリクエストを送信
response = requests.post(token_url, data=data)

# レスポンスからアクセストークンを取得
if response.status_code == 200:
    tokens = response.json()
    access_token = tokens['access_token']
    print("Access Token:", access_token)
    # 必要に応じてリフレッシュトークンも取得できます
    # refresh_token = tokens.get('refresh_token')
else:
    print("Error while retrieving the access token:", response.text)

以上 boxsdkライブラリーを使わずにOAuth2.0認証フローを実装してみました。


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