Authlib で OAuth 2.0 の仕組みや使い方を学ぶ

スポンサーリンク

はじめに

Web サービスで認証するにあたり、OAuth 2.0 という言葉をよく耳にするようになりました。

一方で、OAuth 2.0 を体系立てて説明している記事が少なかったり、実際にどうやって実装すれば良いのかわからなかったりしました。

そこで本記事では、以下の順で OAuth 2.0 を理解し、実装できるように執筆しました。

  1. OAuth 2.0 の仕様を紹介
  2. Authlib ライブラリを利用し、OAuth 2.0 サーバーとクライアントを実装

なお、SSO について学習する場合は、以下の順で学習することをおすすめします。

スポンサーリンク

OAuth 2.0 とは

OAuth 2.0 とは、ユーザーに代わって、アプリケーションが API サーバー等のアクセストークン (認可) を取得する仕様です。

OAuth 2.0 では認証も可能ですが、以下の図のように認証のために鍵を直接アプリに渡すため、非常に危険です。(認可と認証の違いはこちら)

上:OpenID Connect (OIDC)、下:OAuth 2.0
https://en.wikipedia.org/wiki/OAuth

そのため、本番環境では安全性の高い OpenID Connect (OIDC) を利用して認証してください。

OAuth 2.0 のフローとロール

OAuth 2.0 は以下のフローでアクセストークンを取得します。

token = アクセストークン
https://docs.authlib.org/en/latest/oauth/2/intro.html#intro-oauth2
1. Login 画面の例
2. Resource owner grant 画面の例

なお、上図のとおり OAuth 2.0 には 4つのロール (登場人物) が存在します。

  • Client:アプリケーションに相当
  • Resource Owner:ユーザーに相当
  • Resource Server:API サーバーに相当
  • Authorization Server:クライアントがアクセストークンを取得するためのサーバーに相当

OAuth defines four roles:

・resource owner
・resource server
・client
・authorization server

The 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 のフローの①、②の動作を決定します。

https://docs.authlib.org/en/latest/oauth/2/intro.html#intro-oauth2

Grant Type には以下の4つのタイプがあります。

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 でアクセストークンを取得する手順は以下のとおりです。

https://datatracker.ietf.org/doc/html/rfc6749#section-4.3
  1. ユーザーが Password Credentials (パスワード) を入力する (A)
  2. アプリケーションは、Authorization Server に Password Credentials を渡し、アクセストークンをリクエスト (B)
  3. Authorization Server は、アクセストークンを返す (C)

Client Credentials Grant

Client Credentials Grant は、アプリケーションが持つ認証情報 (Credentials) で認証し、アクセストークン (認可) を取得する方法です。
つまり、ユーザーはパスワードを入力しません。

Client Credentials Grant でアクセストークンを取得する手順は以下のとおりです。

https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
Client = アプリケーション
  1. アプリケーション認証情報 (Credentials) で、Authorization Server にアクセストークンをリクエスト (A)
  2. Authorization Server はアクセストークンを返す (B)

Authorization Code Grant

Authorization Code Grant は、Authorization Code とアクセストークンを交換することでアクセストークンを取得する方法です。

Authorization Code Grant でアクセストークンを取得する手順は以下のとおりです。

https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
Resource Owner = ユーザー、User-Agent = ブラウザ、Client = アプリケーション
  1. アプリケーションがブラウザ経由で Authorization Server にクライアント識別子を渡す (A)
  2. ブラウザが Authorization Server のリダイレクト結果 (認証画面) を表示し、ユーザーが認証 (B)
  3. Authorization Server は認証に成功すると Authorization Code を返す (C)
  4. アプリケーションは、Authorization Server に Authorization Code を渡し、アクセストークンをリクエスト (D)
  5. 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

詳細を知りたい方はこのあたりを見てください。

RFC 6749 - The OAuth 2.0 Authorization Framework 日本語訳
スポンサーリンク

OAuth 2.0 サーバーの実装

以下の GitHub のコードを利用して、OAuth 2.0 サーバーを実装します。

GitHub - authlib/example-oauth2-server: Example for OAuth 2 Server for Authlib.
Example for OAuth 2 Server for Authlib. Contribute to authlib/example-oauth2-server development by creating an account on GitHub.

なお、上記のコードでは Flask を利用してます。Flask については以下の記事をご覧ください。

実装する OAuth 2.0 サーバーは以下のとおりです。

  • Authorization Server:
    • http://127.0.0.1:5000/oauth/token (アクセストークン発行)
    • http://127.0.0.1:5000/oauth/authorize (Authorization Code 発行)
  • Resource Server:
    • http://127.0.0.1:5000/api/me (REST API サーバー)
https://docs.authlib.org/en/latest/oauth/2/intro.html#intro-oauth2

FlaskでOAuth 2.0 認可サーバー

git clone https://github.com/authlib/example-oauth2-server.git
cd example-oauth2-server/
pip3 install -r requirements.txt
export AUTHLIB_INSECURE_TRANSPORT=1
flask run -h 0.0.0.0 -p 5000

これで OAuth 2.0 サーバーの構築は完了です。

Authorization Server に Client を登録

Authorization ServerClient (アプリケーション) を登録します。

まずは http://127.0.0.1:5000/ にアクセスし、Resource Owner (ログインユーザー) を登録します。

今回は「test」ユーザーを作成し、[Login/Signup] をクリック

次に [Create Client] をクリックして、Client (アプリケーション) を登録します。

入力する情報は以下のとおりです。

https://github.com/authlib/example-oauth2-server

[Submit] をクリックすると、 作成した Client の情報が表示されます。

export client_secret='<上記で取得した client_secret の値>'
export client_id='<上記取得した client_id の値>'
export username='test'

OAuth 2.0 クライアントの実装

今回は次の2つの方法で OAuth 2.0 クライアントを実装します。

https://docs.authlib.org/en/latest/oauth/2/intro.html#intro-oauth2

curl で OAuth 2.0 を実装

curl を利用して、以下の2種類の Grant Type でアクセストークンを取得します。

Resource Owner Password Credentials Grant を利用

curl + Resource Owner Password Credentials Grant でアクセストークンを取得して、REST API を実行します。

https://datatracker.ietf.org/doc/html/rfc6749#section-4.3
Resource Owner = ユーザー、Client = アプリケーション
OAuth 2.0 のフローの①、②に相当

以降では、上記の図のフローに従って実際にクライアントの curl を操作します。

  • 上図 (A) の Resource Owner は、curl のコマンドを入力するあなた自身です。
  • 上図 (C) は、curl のレスポンスです。
curl -u ${client_id}:${client_secret} -XPOST http://127.0.0.1:5000/oauth/token -F grant_type=password -F username=${username} -F password=valid -F scope=profile
{"access_token": "afsdgabcdefghijklmn", "expires_in": 864000, "refresh_token": "fagshabcdefghijklmn", "scope": "profile", "token_type": "Bearer"}

curl + Resource Owner Password Credentials Grant でアクセストークンを取得できました。

アクセストークンを利用して REST API を叩く
export access_token='<上記で取得した access_token の値>'
curl -H "Authorization: Bearer ${access_token}" http://127.0.0.1:5000/api/me
{"id":1,"username":"test"}

アクセストークンを利用して、/api/me API が実行できたことが確認できます。

補足:アクセストークン無い時
curl http://127.0.0.1:5000/api/me
{"error": "missing_authorization", "error_description": "Missing \"Authorization\" in headers."}

REST API の実行を許可しないことが確認できます。

Authorization Code Grant を利用

curl + Authorization Code Grant で、アクセストークンを取得して API を実行します。

https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
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)

赤線で囲った文字列が Authorization code
code=<上記で取得した Authorization code の値>
curl -u ${client_id}:${client_secret} -XPOST http://127.0.0.1:5000/oauth/token -F grant_type=authorization_code -F scope=profile -F code=${code}
{"access_token": "123456789ABCDEFGHIJK", "expires_in": 864000, "scope": "profile", "token_type": "Bearer"}
アクセストークンを利用して REST API を叩く
export access_token=<上記で取得した access_token の値>
curl -H "Authorization: Bearer ${access_token}" http://127.0.0.1:5000/api/me
{"id":1,"username":"test"}

Requests (Python) で OAuth 2.0 を実装

Python の Requests ライブラリを利用して、Resource Owner Password Credentials Grant の Grant Type でアクセストークンを取得します。

https://datatracker.ietf.org/doc/html/rfc6749#section-4.3
Resource Owner = ユーザー、Client = アプリケーション
OAuth 2.0 のフローの①、②に相当

Resource Owner Password Credentials Grant の実装

pip3 install requests
vim password_flow_requests.py
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 の結果
python3 password_flow_requests.py
{"id":1,"username":"test"}

リソースサーバーの /api/me API を叩き、結果を取得していることが確認できます。

OAuth 2.0 のフローと Python のソースコードの対応

OAuth 2.0 のフローと、Python のソースコードの対応関係は以下のとおりです。

https://docs.authlib.org/en/latest/oauth/2/intro.html#intro-oauth2
  • ①: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 ソースコードの対応関係は以下のとおりです。

https://datatracker.ietf.org/doc/html/rfc6749#section-4.3
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 (シングルサインオン) 入門記事の続きは以下のとおりです。


参考資料

https://datatracker.ietf.org/doc/html/rfc6749

Introduce OAuth 2.0