Python OAuth認証の定番!oauthlibライブラリ徹底解説 🔑

Web開発

現代のWebサービス開発において、ユーザーの認証や認可は避けて通れない重要な要素です。特に、外部サービスとの連携やAPIアクセスにおいては、OAuthというプロトコルが広く利用されています。PythonでOAuthを扱う際、中核となる強力なライブラリがoauthlibです。

oauthlibは、OAuth 1.0a (RFC 5849) および OAuth 2.0 (RFC 6749) の仕様に準拠した、汎用的かつ徹底的な実装を提供します。このライブラリの最大の特徴は、特定のHTTPリクエストライブラリやWebフレームワークに依存しない点です。これにより、requests, httpx, urllibなど、好みのHTTPクライアントと組み合わせて利用したり、Django, Flask, PyramidなどのWebフレームワークにOAuthクライアント機能やプロバイダー機能を組み込んだりすることが可能です。

なぜoauthlibを選ぶのでしょうか? 🤔

  • 仕様準拠: OAuthの複雑な仕様を正確に実装しており、信頼性が高いです。
  • 柔軟性: 特定の通信ライブラリに縛られず、自由な設計が可能です。
  • 拡張性: コアロジックを提供し、様々なフレームワークとの統合ライブラリ(例: requests-oauthlib, django-oauth-toolkit)の基盤となっています。
  • セキュリティ: PKCE (Proof Key for Code Exchange) のような最新のセキュリティ推奨事項にも対応しています。

このブログでは、oauthlibの基本的な概念から、主要な機能、そして具体的な使い方まで、深く掘り下げて解説していきます。

oauthlibが多くの開発者に選ばれる理由となる、いくつかの重要な特徴を見ていきましょう。

フレームワーク非依存 (Framework Agnostic)
前述の通り、oauthlibはHTTPリクエストの送受信やWebフレームワークの機能に直接依存しません。OAuthのプロトコルロジック(リクエストの署名、トークンの検証、パラメータの組み立てなど)に特化しています。これにより、プロジェクトで使用している技術スタックに関わらず、OAuthのコア機能を一貫した方法で利用できます。
OAuth 1.0a & OAuth 2.0 サポート
現在主流のOAuth 2.0はもちろん、一部のレガシーなサービスで利用されているOAuth 1.0aにも対応しています。これにより、幅広いサービスとの連携が可能です。
豊富なグラントタイプ対応 (OAuth 2.0)
OAuth 2.0には、ユースケースに応じて複数の「グラントタイプ」(認可フロー)が定義されています。oauthlibは主要なグラントタイプを網羅的にサポートしています。
  • Authorization Code Grant (認可コードグラント): Webサーバーアプリケーションで最も一般的に使用されるフロー。セキュリティが高いです。
  • Implicit Grant (インプリシットグラント): 元々はJavaScriptなどのクライアントサイドアプリケーション向けでしたが、セキュリティ上の懸念から現在では非推奨とされ、Authorization Code Grant + PKCEの利用が推奨されます。
  • Resource Owner Password Credentials Grant (リソースオーナーパスワードクレデンシャルグラント): ユーザーのID/パスワードをクライアントが直接扱うフロー。信頼できるクライアント(例: 公式アプリ)に限定され、一般的には非推奨です。
  • Client Credentials Grant (クライアントクレデンシャルグラント): クライアント自身の認証情報でアクセストークンを取得するフロー。マシン間の通信などに用いられます。
  • Refresh Token Grant (リフレッシュトークングラント): 有効期限の切れたアクセストークンを、ユーザーの再認証なしに更新するためのフロー。
  • PKCE (Proof Key for Code Exchange) のサポート: 特にモバイルアプリやシングルページアプリケーション(SPA)のような公開クライアント (Public Client) において、認可コード横取り攻撃を防ぐための重要な拡張仕様です。oauthlibはPKCEの生成と検証をサポートしています。
拡張性とカスタマイズ性
oauthlibは、特にサーバーサイド(プロバイダー)の実装において、バリデーター (Validator) クラスを通じてアプリケーション固有のロジック(ユーザー認証、クライアント検証、トークン保存など)を組み込めるように設計されています。これにより、既存のシステムへの統合が容易になります。
OpenID Connect (OIDC) への対応
oauthlibはOpenID Connectの基本的な要素もサポートしており、認証機能の実装にも利用できます。IDトークンの処理などが含まれます。

oauthlibのインストールは、Pythonのパッケージ管理ツールであるpipを使って簡単に行えます。

pip install oauthlib

もし、JWTベースの機能(例: Signed Token)やRSA署名など、追加の暗号化機能を利用したい場合は、関連する依存ライブラリも一緒にインストールする必要があります。signedtoken エクストラを指定することで、必要なライブラリ(cryptography, pyjwt)が一緒にインストールされます。

pip install 'oauthlib[signedtoken]'

注意: シェルによっては角括弧 [] が特殊文字として解釈される場合があるため、引用符で囲むことが推奨されます。

また、一部のLinuxディストリビューションでは、システムのパッケージマネージャー(apt, yum, zypper, pacmanなど)を通じてインストールすることも可能です。

oauthlibはOAuthクライアント(APIを利用する側)とOAuthプロバイダー(APIを提供する側)の両方の実装をサポートしますが、まずはクライアントサイドでの利用方法を見ていきましょう。ここでは、最も一般的なOAuth 2.0の認可コードグラント (Authorization Code Grant) を例に説明します。

認可コードグラントの大まかな流れは以下の通りです。

  1. クライアントは、認可サーバーに対して認可リクエストURLを生成し、ユーザーをリダイレクトします。
  2. ユーザーは認可サーバー上で認証し、クライアントへのアクセス許可を与えます。
  3. 認可サーバーは、指定されたリダイレクトURIにユーザーをリダイレクトさせ、その際に「認可コード」を付与します。
  4. クライアントは受け取った認可コードと自身のクライアント認証情報を使い、認可サーバーにアクセストークンをリクエストします。
  5. 認可サーバーは認可コードとクライアント情報を検証し、問題なければアクセストークンと(オプションで)リフレッシュトークンを発行します。
  6. クライアントは取得したアクセストークンを使って、保護されたリソース(API)にアクセスします。

oauthlibでは、このフローの各ステップに対応するメソッドが用意されています。oauthlib.oauth2.WebApplicationClient クラスが主に使われます。

1. WebApplicationClientの初期化

from oauthlib.oauth2 import WebApplicationClient

client_id = 'YOUR_CLIENT_ID'  # あなたのクライアントID
client = WebApplicationClient(client_id)

2. 認可リクエストURLの生成

ユーザーをリダイレクトさせるためのURLを生成します。スコープ(要求する権限)やリダイレクトURIを指定します。PKCEを使用する場合は、code_challengecode_challenge_method もここで設定します。

authorization_endpoint = 'https://provider.example.com/authorize'
redirect_uri = 'https://client.example.com/callback'
scope = ['profile', 'email']

# PKCE用のコードベリファイアとコードチャレンジを事前に生成しておく必要があります
# (例: code_verifier = secrets.token_urlsafe(40), code_challenge = hashlib.sha256(code_verifier.encode('utf-8')).digest(), ...)
# code_challenge_method='S256' が推奨されます

uri = client.prepare_request_uri(
    authorization_endpoint,
    redirect_uri=redirect_uri,
    scope=scope,
    # state=generate_random_state() # CSRF対策のため、stateパラメータを生成して付与することを強く推奨
    # code_challenge=generated_code_challenge, # PKCEを使用する場合
    # code_challenge_method='S256'          # PKCEを使用する場合
)

print(f"リダイレクト先URL: {uri}")
# ここでユーザーを uri にリダイレクトさせる処理(例: Flaskなら return redirect(uri))

⚠️ 重要: state パラメータはクロスサイトリクエストフォージェリ(CSRF)攻撃を防ぐために不可欠です。リクエスト前にランダムな値を生成してセッションなどに保存し、コールバック時に返ってきた値と一致するか検証する必要があります。

3. コールバック処理と認可コードの取得

ユーザーが認可サーバーで許可を与えると、指定したredirect_uriにリダイレクトされ、URLパラメータとしてcode(認可コード)とstateが付与されます。

# Webフレームワークの処理内でリクエストURL全体を取得 (例: request.url)
callback_url = 'https://client.example.com/callback?code=AUTHORIZATION_CODE_RECEIVED&state=STATE_RECEIVED'

# state の検証 (保存しておいた値と比較)
# if received_state != saved_state:
#     raise SecurityException("Invalid state parameter")

# URLから認可コードなどをパース
authorization_response = client.parse_request_uri_response(callback_url, state=saved_state)
code = authorization_response.get('code')

if not code:
    # エラー処理
    error = authorization_response.get('error')
    print(f"認可エラー: {error}")
else:
    print(f"認可コード取得成功: {code}")

4. アクセストークンのリクエスト準備

取得した認可コードを使って、アクセストークンをリクエストするための情報を準備します。クライアントシークレットやPKCEのコードベリファイアもここで含めます。

token_endpoint = 'https://provider.example.com/token'
client_secret = 'YOUR_CLIENT_SECRET' # あなたのクライアントシークレット

# PKCEを使用した場合、ここで code_verifier を渡す
request_body = client.prepare_request_body(
    code=code,
    redirect_uri=redirect_uri,
    client_secret=client_secret,
    # code_verifier=generated_code_verifier # PKCEを使用した場合
)

print(f"トークンリクエストボディ: {request_body}")
# この request_body を使って、token_endpoint に POSTリクエストを送信する
# 例: requests.post(token_endpoint, data=request_body, auth=(client_id, client_secret) または headers=headers)

5. アクセストークンのレスポンス解析

トークンエンドポイントからのレスポンス(通常はJSON形式)を受け取り、oauthlibで解析してアクセストークンなどを取得します。

# トークンエンドポイントからのレスポンスボディ (例: response.text)
token_response_body = '{"access_token": "ACCESS_TOKEN_HERE", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "REFRESH_TOKEN_HERE", "scope": "profile email"}'

token_info = client.parse_request_body_response(token_response_body, scope=scope)

access_token = token_info.get('access_token')
token_type = token_info.get('token_type')
expires_in = token_info.get('expires_in')
refresh_token = token_info.get('refresh_token')
received_scope = token_info.get('scope') # 実際に付与されたスコープ

print(f"アクセストークン: {access_token}")
print(f"リフレッシュトークン: {refresh_token}")

6. 保護されたリソースへのアクセス

取得したアクセストークンを使って、APIにリクエストを送信します。通常、HTTPヘッダーの `Authorization` に `Bearer {access_token}` の形式で設定します。

この部分はoauthlibの直接の機能ではありませんが、requests-oauthlibなどの連携ライブラリを使うと、トークンを自動で付与してくれます。自前で実装する場合は、HTTPクライアントライブラリの機能を使います。

import requests # requestsライブラリを使う場合

api_url = 'https://provider.example.com/api/userinfo'
headers = {'Authorization': f'Bearer {access_token}'}

response = requests.get(api_url, headers=headers)

if response.status_code == 200:
    user_data = response.json()
    print(f"APIレスポンス: {user_data}")
else:
    print(f"APIアクセスエラー: {response.status_code}, {response.text}")

これらのステップは、oauthlibが提供するコアな機能を示しています。実際には、HTTPリクエストの送信、セッション管理、エラーハンドリングなどは、使用するWebフレームワークやHTTPクライアントライブラリと組み合わせて実装する必要があります。requests-oauthlibのようなライブラリは、これらの連携部分を簡略化してくれます。

PKCE(ピクシーと読むことも)は、OAuth 2.0の認可コードグラントにおけるセキュリティを強化するための拡張仕様 (RFC 7636) です。特に、クライアントシークレットを安全に保持できない公開クライアント(モバイルアプリ、SPAなど)において、認可コード横取り攻撃を防ぐために重要です。

仕組み:

  1. クライアントは、リクエストごとにランダムな文字列「コードベリファイア (code verifier)」を生成します。
  2. クライアントは、コードベリファイアから「コードチャレンジ (code challenge)」を計算します(通常はSHA256ハッシュ化)。
  3. 認可リクエスト時に、コードチャレンジとその計算方法(code_challenge_method、通常はS256)を認可サーバーに送信します。
  4. 認可サーバーは、コードチャレンジと計算方法を認可コードに関連付けて保存します。
  5. クライアントが認可コードを使ってアクセストークンをリクエストする際に、元のコードベリファイアを送信します。
  6. 認可サーバーは、受け取ったコードベリファイアを保存しておいた計算方法で変換し、保存しておいたコードチャレンジと一致するか検証します。一致すればトークンを発行し、一致しなければ拒否します。

これにより、仮に悪意のあるアプリが認可コードを横取りしても、元のコードベリファイアを知らないため、アクセストークンを取得することができません。

oauthlibは、prepare_request_uri メソッドで code_challengecode_challenge_method を、prepare_request_body メソッドで code_verifier を指定することでPKCEをサポートします。

💡 現在では、Webサーバーアプリケーションのような機密クライアント (Confidential Client) であっても、PKCEを利用することが推奨されています。多層防御の観点からセキュリティが向上します。

oauthlibは認可コードグラント以外にも、様々なグラントタイプに対応するクライアントクラスを提供しています。

  • Client Credentials Grant: マシン間通信など、ユーザーの介在なしにクライアント自身がリソースアクセスする場合に使用します。oauthlib.oauth2.BackendApplicationClient を利用します。主にクライアントIDとクライアントシークレットを使って直接アクセストークンを取得します。
    from oauthlib.oauth2 import BackendApplicationClient
    from requests_oauthlib import OAuth2Session # requests-oauthlib を使うと便利
    
    client_id = 'YOUR_CLIENT_ID'
    client_secret = 'YOUR_CLIENT_SECRET'
    token_url = 'https://provider.example.com/token'
    scope = ['read_reports'] # 必要なスコープ
    
    client = BackendApplicationClient(client_id=client_id, scope=scope)
    oauth_session = OAuth2Session(client=client)
    
    try:
        token = oauth_session.fetch_token(
            token_url=token_url,
            client_id=client_id,
            client_secret=client_secret
        )
        print(f"取得したトークン: {token}")
        # この token['access_token'] を使ってAPIにアクセス
    except Exception as e:
        print(f"トークン取得エラー: {e}")
  • Resource Owner Password Credentials Grant: ユーザーのユーザー名とパスワードをクライアントが直接預かり、それを使ってアクセストークンを取得します。信頼できる第一者クライアント以外での利用は強く非推奨です。oauthlib.oauth2.LegacyApplicationClient を利用します。
  • Refresh Token Grant: アクセストークンの有効期限が切れた際に、リフレッシュトークンを使って新しいアクセストークンを取得します。OAuth2Session (requests-oauthlibなど) や WebApplicationClient など、多くのクライアント実装で自動/手動リフレッシュ機能がサポートされています。
    # requests-oauthlib の OAuth2Session を使った例
    # (自動リフレッシュを設定している場合)
    # 通常通り APIにアクセスしようとすると、トークンが切れていれば
    # 自動的にリフレッシュ処理が行われ、新しいトークンで再試行されることが多い
    
    # 手動でリフレッシュする場合
    refresh_token = 'SAVED_REFRESH_TOKEN'
    client_id = 'YOUR_CLIENT_ID'
    client_secret = 'YOUR_CLIENT_SECRET'
    token_url = 'https://provider.example.com/token'
    
    client = WebApplicationClient(client_id) # または他の適切なClientクラス
    oauth_session = OAuth2Session(client=client, token={'refresh_token': refresh_token})
    
    try:
        new_token = oauth_session.refresh_token(
            token_url,
            client_id=client_id,
            client_secret=client_secret
        )
        print(f"更新されたトークン: {new_token}")
        # new_token を保存し、今後のAPIアクセスに利用
    except Exception as e:
        print(f"トークンリフレッシュエラー: {e}")

oauthlibは、OAuthプロバイダー(認可サーバーやリソースサーバー)を構築するための機能も提供します。プロバイダー側の実装はクライアント側よりも複雑で、アプリケーションのデータストア(ユーザー情報、クライアント情報、トークン情報など)との連携が不可欠です。

中心となるのは oauthlib.oauth2.RequestValidator という抽象基底クラスです。このクラスを継承し、特定のメソッドをアプリケーション固有のロジックで実装する必要があります。

RequestValidatorで実装が必要な主要メソッド (例)

メソッド名 役割
validate_client_id(client_id, request) 指定されたクライアントIDが存在し、有効か検証する。
validate_redirect_uri(client_id, redirect_uri, request) 指定されたリダイレクトURIが、そのクライアントに登録されているものと一致するか検証する。
validate_scopes(client_id, scopes, client, request) リクエストされたスコープが、そのクライアントに許可されている範囲内か検証する。
validate_grant_type(client_id, grant_type, client, request) リクエストされたグラントタイプが、そのクライアントに許可されているか検証する。
validate_response_type(client_id, response_type, client, request) リクエストされたレスポンスタイプ(例: ‘code’, ‘token’)が許可されているか検証する。
authenticate_client(request) クライアント認証(例: Basic認証やリクエストボディ内の情報)を行い、クライアントオブジェクトを返す。認証失敗時はFalseを返す。
save_authorization_code(client_id, code, request) 生成された認可コードを、関連情報(ユーザー、スコープ、リダイレクトURIなど)と共に保存する。
validate_code(client_id, code, client, request) 提供された認可コードが有効か(存在するか、期限切れでないか、使用済みでないかなど)検証する。
confirm_redirect_uri(client_id, code, redirect_uri, client, request) トークンリクエスト時のリダイレクトURIが、認可コード発行時のものと一致するか検証する(認可コードグラントの場合)。
save_bearer_token(token, request) 生成されたアクセストークンとリフレッシュトークンを保存する。
validate_bearer_token(token, scopes, request) リソースアクセス時に提供されたアクセストークンが有効か(存在するか、期限切れでないか、要求されたスコープを持っているか)検証する。
get_default_redirect_uri(client_id, request) クライアントに登録されているデフォルトのリダイレクトURIを返す(必要な場合)。
invalidate_authorization_code(client_id, code, request) 使用済みの認可コードを無効化する。
validate_user(username, password, client, request) リソースオーナーパスワードクレデンシャルグラントで使用。ユーザーの認証情報を検証する。
validate_refresh_token(refresh_token, client, request) リフレッシュトークングラントで使用。リフレッシュトークンが有効か検証する。
validate_code_challenge(code_challenge, code_challenge_method, request) PKCEで使用。提供されたコードチャレンジとメソッドが有効か検証する。

これらのメソッドを実装したカスタムValidatorクラスを作成したら、oauthlib.oauth2.Server クラス(または WebApplicationServer, AuthorizationEndpoint, TokenEndpoint などのより具体的なクラス)にそのValidatorを渡して、OAuthプロバイダーのインスタンスを作成します。

from oauthlib.oauth2 import Server
from myapp.validators import MyCustomRequestValidator # 上記メソッドを実装したクラス

validator = MyCustomRequestValidator()
server = Server(validator)

# Webフレームワークのエンドポイント内でserverのメソッドを呼び出す
# 例: 認可エンドポイント
# uri, headers, body, status = server.create_authorization_response(
#     uri=request.uri, http_method=request.method, body=request.body, headers=request.headers
# )
# return Response(body, status=status, headers=headers)

# 例: トークンエンドポイント
# headers, body, status = server.create_token_response(
#     uri=request.uri, http_method=request.method, body=request.body, headers=request.headers
# )
# return Response(body, status=status, headers=headers)

# 例: リソース保護
# is_valid, oauth_request = server.verify_request(
#     uri=request.uri, http_method=request.method, body=request.body, headers=request.headers
# )
# if is_valid:
#     # アクセス許可
#     # oauth_request.user, oauth_request.client, oauth_request.scopes などが利用可能
# else:
#     # アクセス拒否

サーバーサイドの実装は、選択するWebフレームワークやデータベースによって具体的なコードが大きく異なります。Djangoであれば django-oauth-toolkit、Flaskであれば Authlibflask-oauthlibは古い可能性があります)のようなライブラリが、oauthlibをベースにしてより高レベルな機能を提供しており、実装を簡素化してくれます。

oauthlibはOAuth 2.0だけでなく、OAuth 1.0aのプロトコルロジックもサポートしています。Twitter APIの旧バージョンなど、一部のサービスではまだOAuth 1.0aが使われています。

基本的な考え方はOAuth 2.0と同様で、クライアントサイドでは oauthlib.oauth1.Client クラス、サーバーサイドでは oauthlib.oauth1.Server クラスと、対応するValidatorを使用します。OAuth 1.0aはリクエスト署名の計算が特徴的ですが、oauthlibがその複雑な部分をハンドリングしてくれます。

from oauthlib.oauth1 import Client
import requests

# OAuth 1.0a クライアントの例 (requests との組み合わせ)
client_key = 'YOUR_CLIENT_KEY'
client_secret = 'YOUR_CLIENT_SECRET'
resource_owner_key = 'USER_ACCESS_TOKEN'
resource_owner_secret = 'USER_ACCESS_TOKEN_SECRET'

client = Client(client_key, client_secret=client_secret,
                resource_owner_key=resource_owner_key, resource_owner_secret=resource_owner_secret)

uri = 'https://api.example.com/resource'
http_method = 'GET'
body = None
headers = {}

# リクエスト署名付きのURI, ヘッダー, ボディを生成
signed_uri, signed_headers, signed_body = client.sign(
    uri, http_method=http_method, body=body, headers=headers
)

# requests でリクエスト送信
response = requests.get(signed_uri, headers=signed_headers, data=signed_body)

print(response.status_code)
print(response.text)

requests-oauthlib を使うと、OAuth 1.0aの認証も OAuth1SessionOAuth1認証クラスを使ってより簡単に扱えます。

oauthlib自体は低レベルなライブラリですが、その上に構築された多くの高レベルなライブラリが存在し、特定のWebフレームワークやHTTPクライアントとの統合を容易にしています。

  • requests-oauthlib: Pythonの人気HTTPクライアント requests とoauthlibを統合します。OAuth1Session, OAuth2Session クラスを提供し、認証情報の管理やトークンの自動更新などを簡単に行えます。クライアントサイドの実装で非常によく使われます。
  • django-oauth-toolkit: DjangoフレームワークでOAuth 2.0プロバイダーを構築するための包括的なライブラリです。oauthlibを内部で使用しており、Djangoのモデルやビューとの連携をスムーズに行えます。Django REST frameworkとの統合もサポートしています。
  • Authlib: oauthlibとは別のライブラリですが、oauthlibにインスパイアされ、よりモダンな機能(JWT, JWS, JWE, OpenID Connectの高度なサポートなど)を提供します。Flask, Django, Starletteなどのフレームワーク向けの統合機能も組み込まれており、近年人気が高まっています。OAuth 2.0/OIDCプロバイダーやクライアントの実装に適しています。
  • Flask-OAuthlib / Flask-Dance: Flaskフレームワーク向けのOAuthクライアント/プロバイダー機能を提供します。(Flask-OAuthlibは開発が停滞している可能性があるため、AuthlibFlask-Dance の利用を検討すると良いでしょう。)
  • Pyramid-oauthlib, Bottle-oauthlib: それぞれPyramid, Bottleフレームワーク向けのoauthlib統合ライブラリです。
  • google-auth-oauthlib: Google APIにアクセスする際のOAuth 2.0フローを扱うためのライブラリで、google-auth ライブラリと連携して動作します。内部でoauthlibを利用しているかは直接言及されていませんが、Google Cloud PlatformやGoogle Workspace APIを利用する際に推奨されています。

これらのライブラリは、oauthlibのコア機能を利用しつつ、特定の環境での定型的な処理を抽象化してくれるため、開発効率を大幅に向上させることができます。プロジェクトの要件に合わせて適切なライブラリを選択することが重要です。

oauthlibは、PythonでOAuth 1.0aおよびOAuth 2.0の複雑なロジックを扱うための、仕様に準拠した堅牢で柔軟な基盤ライブラリです。フレームワーク非依存であるため、様々な環境で利用でき、多くの高レベルなOAuth関連ライブラリのバックボーンとなっています。

クライアントサイドでは認可フローの各ステップを精密に制御でき、サーバーサイドではValidatorパターンを通じてアプリケーション固有のロジックを組み込むことが可能です。PKCEのような最新のセキュリティ標準にも対応しており、安全な認証・認可システムを構築するための強力なツールとなります。

直接oauthlibを使うことでOAuthプロトコルの詳細な理解が深まりますが、requests-oauthlibdjango-oauth-toolkitAuthlibのような連携ライブラリを利用することで、より迅速な開発が可能になります。

OAuthは現代のWeb開発において不可欠な技術であり、oauthlibはその実装を支える重要なPythonライブラリの一つです。ぜひ公式ドキュメントも参照しながら、その強力な機能を活用してみてください。🚀

もっと詳しく知りたい方へ:
oauthlibの公式ドキュメントは Read the Docs で公開されています。より詳細な使い方やAPIリファレンスを確認できます。

コメント

タイトルとURLをコピーしました