OAuth(Open Authorizationの略)をアプリケーションに組み込むことは、多くのエントリーレベルのソフトウェアエンジニアにとってはやや威圧的で落胆させるかもしれません。結局のところ、正しい設定で複雑な認証プロセスを実装するのに何時間、あるいは何日も費やす必要がありますが、実際にはほんの小さな機能を完成させただけだと気づくのです。多くの労力を伴うにもかかわらず、OAuthはユーザーがウェブサイトやアプリケーションに個人情報へのアクセスを安全かつ効率的に許可する方法です。したがって、OAuthの仕組みを理解する努力は価値があります。この記事では、OAuthのプロセスに関わるすべてのステップを分解し、あなたのOAuthの旅をスタートさせるお手伝いをします。
チュートリアルに入る前に注意すべきことが2つあります。
- この記事で使用するOAuthバージョンはOAuth 2.0で、これは現在の業界標準です。'https'(TLS)はセキュリティ確保のために必要です。
- PKCEは任意ですが、OAuthプロセス全体をより安全にするため、このチュートリアルに含めることにしました。
アプリケーションが2つあると仮定します:App A(クライアントアプリケーション)とApp B(認証サーバー)。App AはApp Bに保存されているユーザーXの情報にアクセスしたいと考えています。App Aがこの潜在的に機密性の高い情報に安全にアクセスできるように、OAuthプロセスを確立する必要があります。
ステップ1:認可URL生成
アプリAがアプリBでユーザーXの情報にアクセスするためには、まずユーザーXの本人確認が必要です。アプリBがユーザーXの身元を確認する最も単純な方法は、ユーザーXにアプリBにログインしてもらうことです。しかし、ユーザーXはアプリAを関与せずに直接アプリBにログインしてはいけません。ログインURLにはアプリAの情報を含める必要があり、アプリBがユーザーXの情報を送る目的地であるアプリAを認識できるようにする必要があります。ここで認証URLが重要になります。認証URLとは、アプリAがユーザーXの情報にアクセスしようとしたことをアプリBに知らせるためのURLです。アプリBはその後、ユーザーXを自分のログインページにリダイレクトします。作成には以下の情報が必要です。
- クライアントID
- コード検証器
- コードチャレンジ
- コードチャレンジ方法
- URIのリダイレクト
クライアントIDは、App Bが発行するApp Aの公開識別子です。これはApp BがApp Aの身元を確認するのに役立ちます。クライアントIDがなければ、App Bは信頼不足からApp Aに機密情報を提供することを拒否します。
Code VerifierとCode Challengeは、認証コード付与をより安全にすることを目指したPKCE(Proof Key for Code Exchangeの略称)プロセスの一部です。Code Verifierは要件を満たすランダムなコードであり、Code ChallengeはCode Verifierの変換版です。ステップ1ではCode Challengeだけで十分です。Code Verifierはステップ2まで使いません。
以下のコードを使ってコード検証ツールやコードチャレンジを作成できます。
インポート re
インポートOS
輸入用Base64
インポート ハッシュリブ
code_verifier = base64.urlsafe_b64encode(os.urandom(50)).decode("utf-8")
code_verifier = re.sub("[^a-zA-Z0-9]+", "", code_verifier)
code_challenge = hashlib.sha256(code_verifier.encode("utf-8")).digest()
code_challenge = base64.urlsafe_b64encode(code_challenge).decode("utf-8")
code_challenge = code_challenge.replace("=", "")
コードチャレンジメソッドは、Code VerifierをCode Challengeに変換するためのエンコーディング手法です。通常、OAuth 2.0は上記のコード例と同様にSHA-256をコードチャレンジメソッドとして使用します。
リダイレクトURIは、App BのコールバックAPIで、User Xがログインした後にApp Aがトリガーします。その目的については後ほど詳しく説明します。
上記の情報を集めたら、認証URLの組み立てを始められます。Pythonでは、requests_oauthlibというライブラリを使って作成できます。
from old OAuth2Session からrequests_oauthlib
app_b = OAuth2Session(CLIENT_ID, redirect_uri=REDIRECT_URI)
authorization_url, _ = app_b.authorization_url(AUTH_URL, code_challenge=code_challenge, code_challenge_method="S256")
上記のコード例では、App Aが「OAuth2Session」コンストラクタを用いて、Client IDとRedirect URIをパラメータとして「app_b」インスタンスを初期化します。その後、App Aは「authorization_url」メソッドを使ってAuthorization URLを作成します。パラメータにはApp Bの認証APIの基本URL、Code Challenge、Code Challenge Methodが含まれます。上記のコード例で生成されるAuthorization URLは以下の通りです:
認可URLが準備できたら、アプリAはそれをユーザーXに送信し、開くよう依頼できます。ユーザーXが認証URLを開くと、アプリBはアプリAのユーザーX情報へのアクセス要求を記録し、ユーザーXをアプリBのログインページにリダイレクトします。
ステップ2:トークン取得とコールバックAPIでの情報取得
ユーザーXはアプリBにログインしました。次はどうすればいいのでしょうか?アプリBはリダイレクトレスポンスをユーザーXに送り、リダイレクトURIで指定されたアプリAのコールバックAPIにリダイレクトします。コールバックAPIの目的は二つあります。トークンを取得し、アプリBからユーザーXの情報を取得することです。
まず、App AがApp Bから取得する必要がある2つのトークン、Access TokenとRefresh Tokenから始めましょう。Access Tokenは保護されたリソースへのアクセスを得るために必要です。この場合、App AはApp Bから発行されたAccess Tokenを所持するまでApp BのユーザーXの情報にアクセスできません。Access Tokenは取り消し不可であり、App BはApp Aに送信した後は取り消すことはできません。したがって、Access Tokenは同じトークンで強力かつ危険です(言葉遊びではありません)。潜在的なデータ漏洩を防ぐために、Access Tokenは通常寿命が短いです。
アクセストークンを短期間にすることは、データセキュリティを確保する上で重要な役割を果たします。しかし、ユーザー体験の面で別の問題が生じます。アクセストークンは頻繁に期限切れになるため、ユーザーは新しいアクセストークンを取得するために頻繁に再認証しなければなりません。幸いなことに、私たちにはRefresh Tokenが助けてくれます。Refresh Tokenは、現在のアクセストークンが期限切れになった際に新しいアクセストークンを取得する役割を果たします。例では、App Bは初期のAccess Tokenと一緒にRefresh TokenをApp Aに送ります。現在のAccess Tokenが切れた後にユーザーXに再度App Bにログインを求める代わりに、App AはRefresh Tokenを使ってApp Bから新しいAccess Tokenを取得することができます。
初期のアクセストークンとリフレッシュトークンを取得するには、以下の情報が必要です:
- トークンURL
- 認可コード
- コード検証器
- コードチャレンジ方法
- クライアントID
トークンURL:トークンURLはApp B側でトークンの発行および更新を行うAPIです。
認証コード:認証コードはApp Bが提供するコードです。App BはApp AのコールバックAPIの最後にURLパラメータとして接続します。
コード検証器:コード検証器についての以前の説明を参照してください。
コードチャレンジ:コードチャレンジ方法については、私の以前の説明を参照してください。
クライアントID:以前の説明を参考にしてください。
以下のコード例では、App Bに対してAPI呼び出しを行い、初期のアクセストークン、リフレッシュトークン、そしてアクセストークンの有効期限に関する情報を取得します。
# requests_oauthlibインポート OAuth2Session より
# app_b = OAuth2Session(CLIENT_ID, redirect_uri=REDIRECT_URI)
token = app_b.fetch_token(
token_url=TOKEN_URL、
code=auth_code,
code_verifier=code_verifier、
code_challenge_method='S256',
client_id=CLIENT_ID、
)
access_token = トークン["access_token"]
refresh_token = トークン["refresh_token"]
expires_at = トークン["expires_at"]
expires_in = トークン["expires_in"]
アクセストークン:リフレッシュトークンを使ってアクセストークンを更新するには、以下の情報が必要です:
- トークンURL
- リフレッシュトークン
- クライアントID
- クライアントの秘密
トークンURL:以前の説明でトークンURLをご参照ください。
リフレッシュトークン:リフレッシュトークンについては、以前の説明を参照してください。
クライアントID:以前の説明を参考にしてください。
クライアントシークレットは、OAuthアプリケーションと認証サーバーのみが知る認証情報です。私たちの場合、クライアントシークレットはアプリBがアプリAの身元を確認するのを助けるパスワードとして機能します。
以下のコード例では、アクセストークンを更新するためにアプリBにAPI呼び出しを行います。
# requests_oauthlibインポート OAuth2Session より
# app_b = OAuth2Session(CLIENT_ID, redirect_uri=REDIRECT_URI)
トークン = app_b.refresh_token(
token_url=TOKEN_URL、
refresh_token=refresh_token、
client_id=CLIENT_ID、
client_secret=CLIENT_SECRET
)
access_token = トークン["access_token"]
expires_at = トークン["expires_at"]
expires_in = トークン["expires_in"]
アクセスコードを手に入れたアプリAは、ついにユーザーXの情報をユーザーXの代わりにアプリBから取得できます。アプリAが行うべきことは、アクセストークンをリクエストのヘッダーの一部として、外部アプリケーションにユーザー情報を提供するためにアプリBの指定APIにAPI呼び出しを行うだけです(通常は「/me」で終わります)。
response = requests.request(
「取れ」
ME_URL、
headers={"Authorization": "Bearer {}".format(token["access_token"])},
)
ステップ3:次は何?
App Aの仕様によりますが、App AはCallback APIで以下のことができます:
- アプリAはユーザーXの情報をデータベースに保存できます。
- アプリAはアクセストークンとリフレッシュトークンをセッション内に保存し、ユーザーXの認証状況をアプリBで追跡できます。リフレッシュトークンの期限切れ後、アプリAはユーザーXのセッションが終了したとみなしてユーザーXをログアウトできます。
免責事項
この記事を終える前に、異なる認証サーバー(App B)によって実装が若干異なる場合があることを指摘しておきたいと思います。したがって、特に認証サーバーへのAPI呼び出しにどのパラメータを含めるかを決定する際には、この記事のコード例を参考程度にしていただきたいと思います。クライアントアプリケーション(App A)を実装し、ターゲットの認可サーバー(App B)とどのように相互作用すべきかを設計する際には、どの変数がどのAPI呼び出しに必要かを理解するために、認証サーバーのドキュメントやコードベースを参照してください。
この記事では、クライアントアプリケーションでOAuthプロセスを設定し、別のアプリケーションで保護されたリソースにアクセスする方法を実演しました。また、OAuthプロセスの各ステップに必要な変数とその理由も説明しました。この記事がOAuthをよりよく理解する助けになれば幸いです。