はじめに
Web サービスで認証するにあたり、OAuth 2.0 という言葉をよく耳にするようになりました。
一方で、OAuth 2.0 を体系立てて説明している記事が少なかったり、実際にどうやって実装すれば良いのかわからなかったりしました。
そこで本記事では、以下の順で OAuth 2.0 を理解し、実装できるように執筆しました。
なお、SSO について学習する場合は、以下の順で学習することをおすすめします。
- 【SSO (シングルサインオン) 入門1】ID フェデレーションとは
- 【SSO (シングルサインオン) 入門2】OAuth 2.0 とは ←イマココ
- 【SSO (シングルサインオン) 入門3】OpenID Connect (OIDC) とは
- 【SSO (シングルサインオン) 入門4】Keycloak で SSO を実装
OAuth 2.0 とは
OAuth 2.0 とは、ユーザーに代わって、アプリケーションが API サーバー等のアクセストークン (認可) を取得する仕様です。
OAuth 2.0 では認証も可能ですが、以下の図のように認証のために鍵を直接アプリに渡すため、非常に危険です。(認可と認証の違いはこちら)
そのため、本番環境では安全性の高い OpenID Connect (OIDC) を利用して認証してください。
OAuth 2.0 のフローとロール
OAuth 2.0 は以下のフローでアクセストークンを取得します。
なお、上図のとおり OAuth 2.0 には 4つのロール (登場人物) が存在します。
- Client:アプリケーションに相当
- Resource Owner:ユーザーに相当
- Resource Server:API サーバーに相当
- Authorization Server:クライアントがアクセストークンを取得するためのサーバーに相当
OAuth defines four roles:
・resource owner
・resource server
・client
・authorization serverThe interaction between the authorization server and resource server is beyond the scope of this specification.
https://tools.ietf.org/html/rfc6749#section-1.1
以降では、上記の図を常に頭の片隅に置きながら読み進めてください。
アクセストークンの役割
アクセストークンは以下の役割を持ちます。
- アクセストークンが無いユーザーから、リソースサーバー (API サーバー等) を保護
- アクセストークンが有効な期間は資格情報(ユーザー名・パスワード等)を入力不要
Grant Type (アクセストークンを取得する種類)
Grant Type とは、OAuth 2.0 を利用して、クライアントが Autorization Server からアクセストークン (認可) を取得する方法のことです。
Grant Type は、OAuth 2.0 のフローの①、②の動作を決定します。
Grant Type には以下の4つのタイプがあります。
- Resource Owner Password Credentials Grant
- Client Credentials Grant
- Authorization Code Grant
- Implicit Grant
This specification defines four grant types -- authorization code, implicit, resource owner password credentials, and client credentials -- as well as an extensibility mechanism for defining additional types.
https://datatracker.ietf.org/doc/html/rfc6749
Resource Owner Password Credentials Grant
Resource Owner Password Credentials Grant は、ユーザーが入力したパスワードで認証し、アクセストークン (認可) を取得する方法です。
Resource Owner Password Credentials Grant でアクセストークンを取得する手順は以下のとおりです。
- ユーザーが Password Credentials (パスワード) を入力する (A)
- アプリケーションは、Authorization Server に Password Credentials を渡し、アクセストークンをリクエスト (B)
- Authorization Server は、アクセストークンを返す (C)
Client Credentials Grant
Client Credentials Grant は、アプリケーションが持つ認証情報 (Credentials) で認証し、アクセストークン (認可) を取得する方法です。
つまり、ユーザーはパスワードを入力しません。
Client Credentials Grant でアクセストークンを取得する手順は以下のとおりです。
- アプリケーションは認証情報 (Credentials) で、Authorization Server にアクセストークンをリクエスト (A)
- Authorization Server はアクセストークンを返す (B)
Authorization Code Grant
Authorization Code Grant は、Authorization Code とアクセストークンを交換することでアクセストークンを取得する方法です。
Authorization Code Grant でアクセストークンを取得する手順は以下のとおりです。
- アプリケーションがブラウザ経由で Authorization Server にクライアント識別子を渡す (A)
- ブラウザが Authorization Server のリダイレクト結果 (認証画面) を表示し、ユーザーが認証 (B)
- Authorization Server は認証に成功すると Authorization Code を返す (C)
- アプリケーションは、Authorization Server に Authorization Code を渡し、アクセストークンをリクエスト (D)
- Authorization Server はアクセストークンを返す (E)
Implicit Grant
非推奨となる見込みのため、省略します。
The implicit grant (response type "token") and other response types causing the authorization server to issue access tokens in the authorization response are vulnerable to access token leakage and access token replay as described in Section 4.1, Section 4.2, Section 4.3, and Section 4.6.
https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-14
詳細を知りたい方はこのあたりを見てください。
OAuth 2.0 サーバーの実装
以下の GitHub のコードを利用して、OAuth 2.0 サーバーを実装します。
なお、上記のコードでは Flask を利用してます。Flask については以下の記事をご覧ください。
実装する OAuth 2.0 サーバーは以下のとおりです。
- Authorization Server:
- Resource Server:
FlaskでOAuth 2.0 認可サーバー
これで OAuth 2.0 サーバーの構築は完了です。
Authorization Server に Client を登録
Authorization Server に Client (アプリケーション) を登録します。
まずは http://127.0.0.1:5000/ にアクセスし、Resource Owner (ログインユーザー) を登録します。
次に [Create Client] をクリックして、Client (アプリケーション) を登録します。
入力する情報は以下のとおりです。
[Submit] をクリックすると、 作成した Client の情報が表示されます。
OAuth 2.0 クライアントの実装
今回は次の2つの方法で OAuth 2.0 クライアントを実装します。
curl で OAuth 2.0 を実装
curl を利用して、以下の2種類の Grant Type でアクセストークンを取得します。
Resource Owner Password Credentials Grant を利用
curl + Resource Owner Password Credentials Grant でアクセストークンを取得して、REST API を実行します。
Resource Owner = ユーザー、Client = アプリケーション
OAuth 2.0 のフローの①、②に相当
以降では、上記の図のフローに従って実際にクライアントの curl を操作します。
{"access_token": "afsdgabcdefghijklmn", "expires_in": 864000, "refresh_token": "fagshabcdefghijklmn", "scope": "profile", "token_type": "Bearer"}
curl + Resource Owner Password Credentials Grant でアクセストークンを取得できました。
アクセストークンを利用して REST API を叩く
{"id":1,"username":"test"}
アクセストークンを利用して、/api/me API が実行できたことが確認できます。
補足:アクセストークン無い時
{"error": "missing_authorization", "error_description": "Missing \"Authorization\" in headers."}
REST API の実行を許可しないことが確認できます。
Authorization Code Grant を利用
curl + Authorization Code Grant で、アクセストークンを取得して API を実行します。
Resource Owner = ユーザー、User-Agent = ブラウザ、Client = アプリケーション
1. Authorization Server (http://127.0.0.1:5000/oauth/authorize?response_type=code&client_id=${client_id}&scope=profile) にアクセス (A)
2. (User-Agent (= ブラウザ) が自動で) Authorization Server のページを表示 (B)
3. Consent にチェックを入れ、Submit を押下し、ブラウザが認可サーバーにリクエスト (B)
4. (User-Agent (= ブラウザ) が自動で) Authorization code を表示 (C)
{"access_token": "123456789ABCDEFGHIJK", "expires_in": 864000, "scope": "profile", "token_type": "Bearer"}
アクセストークンを利用して REST API を叩く
{"id":1,"username":"test"}
Requests (Python) で OAuth 2.0 を実装
Python の Requests ライブラリを利用して、Resource Owner Password Credentials Grant の Grant Type でアクセストークンを取得します。
Resource Owner = ユーザー、Client = アプリケーション
OAuth 2.0 のフローの①、②に相当
Resource Owner Password Credentials Grant の実装
from authlib.integrations.requests_client import OAuth2Auth from requests.auth import HTTPBasicAuth import requests import json import os ###認可サーバーからアクセストークンを取得するために、Basic (パスワード) 認証を行う #アドレスとポート番号に注意 authorization_endpoint = 'http://localhost:5000/oauth/token' #Authorization Server basic_auth = HTTPBasicAuth(os.environ['client_id'], os.environ['client_secret']) #環境変数をセットしてください。パスワード認証に必要な情報 form_data = { #Authorization Server に渡す情報の設定 'grant_type': 'password', 'username': os.environ['username'], #環境変数をセットしてください。 'password': 'valid', 'scope': 'profile' } response_authorization = requests.post(authorization_endpoint, auth=basic_auth, data=form_data) #Authorization Server から Bearer 用のアクセストークンを取得 json = json.loads(response_authorization.text) #JSON 文字列を dict に access_token = json['access_token'] #アクセストークンを取得 ###Resource Server の /api/me API を叩くために、アクセストークンを利用して、Bearer 認可を行う #アドレスとポート番号に注意 resource_server_api = 'http://localhost:5000/api/me' #自分の id とユーザー名を表示する API token = {'token_type': 'bearer', 'access_token': access_token} #アクセストークンをセット auth = OAuth2Auth(token) #認可リクエストの作成 response_api = requests.get(resource_server_api, auth=auth) #bearer トークンを使って API を叩く print (response_api.text) # API の結果
{"id":1,"username":"test"}
リソースサーバーの /api/me API を叩き、結果を取得していることが確認できます。
OAuth 2.0 のフローと Python のソースコードの対応
OAuth 2.0 のフローと、Python のソースコードの対応関係は以下のとおりです。
- ①:requests.post(authorization_endpoint, auth=basic_auth, data=form_data)
- ②:response_authorization
- ③:requests.get(api_url, auth=auth)
- ④:response_api
Resource Owner Password Credentials Grant と Python の対応
Resource Owner Password Credentials Grant と Python ソースコードの対応関係は以下のとおりです。
Resource Owner = ユーザー、Client = アプリケーション
OAuth 2.0 のフローの①、②に相当
- (A):${client_id}:${client_secret} をユーザーが入力している時
- (B):requests.post(authorization_endpoint, auth=basic_auth, data=form_data)
- (C):response_authorization
関連記事
SSO (シングルサインオン) 入門記事の続きは以下のとおりです。
- 【SSO (シングルサインオン) 入門1】ID フェデレーションとは
- 【SSO (シングルサインオン) 入門2】OAuth 2.0 とは ←イマココ
- 【SSO (シングルサインオン) 入門3】OpenID Connect (OIDC) とは
- 【SSO (シングルサインオン) 入門4】Keycloak で SSO を実装
参考資料
https://datatracker.ietf.org/doc/html/rfc6749