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

本記事は、OAuth 2.0 に関する記事です。

OpenID Connect (OIDC) については、以下の記事をご覧ください。

フェデレーション ID 等の認証連携については、以下の記事をご覧ください。

OAuth, OIDC について

暗号の基礎

ゼロトラストセキュリティ

スポンサーリンク

OAuth 2.0 とは

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

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

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

https://en.wikipedia.org/wiki/OAuth

アクセストークンの役割

アクセストークンは以下の役割を持ちます。

  • アクセストークンが無いユーザーからリソースサーバー (API サーバー等) を保護
  • アクセストークンが有効な期間は資格情報(ユーザー名・パスワード等)を再入力する必要が無い

OAuth 2.0 のフロー

OAuth 2.0 を利用する一般的なフローを紹介します。

https://docs.authlib.org/en/latest/oauth/2/intro.html#intro-oauth2
token = アクセストークン

以降では、上記の図を常に頭の片隅に置きながら読み進めてください。

OAuth 2.0 のロール

OAuth 2.0 には、4つのロールがあります。

https://docs.authlib.org/en/latest/oauth/2/intro.html#intro-oauth2
  • 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

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

Authorization Code Grant

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

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

  1. アプリケーションがブラウザ経由で Authorization Server にリダイレクト
    (確認画面「*** と連携しますか? はい・いいえ」等が表示されることもある)
  2. ブラウザが Authorization Server のレスポンスを表示
    (一般的には認証画面 (ユーザー名とパスワード) を表示)
  3. 認証画面に入力したユーザー認証で、ブラウザが Authorization Server に Autorization Code をリクエスト
  4. Authorization Server は Authorization Code を返す
  5. アプリケーションは、Autorization Code で Authorization Server にアクセストークンをリクエスト
  6. Authorization Server はアクセストークンを返す
https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
Resource Owner = ユーザー、User-Agent = ブラウザ、Client = アプリケーション

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 日本語訳

Resource Owner Password Credentials Grant

Resource Owner Password Credentials Grant は、ユーザーが入力したパスワードを元に認証し、アクセストークン (認可) を取得する方法です。

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

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

Client Credentials Grant

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

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

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

OAuth 2.0 認可サーバーの実装

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

OAuth 2.0 のフローにおける、次のサーバーを REST API で実装します。

  • 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 サーバー)

REST API については以下の記事をご覧ください。

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

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.

実装としては、Authlib という Python のライブラリ + Flask という Python の Web アプリケーションフレームワークを利用しています。

Flask については以下の記事をご覧ください。

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 を利用して OAuth 2.0 のフローのクライアントを実装します。

[curl] + [Resource Owner Password Credentials Grant] で REST API を実行

「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 を操作します。

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"}

フローA の Resource Owner は、curl のコマンドを入力するあなた自身です。
フローC は curl のレスポンスです。

アクセストークンを利用して 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 が実行できたことが確認できます。

補足:アクセストークン無しで REST API を叩く

アクセストークン無しで /api/me API を叩くと失敗します。

curl http://127.0.0.1:5000/api/me
{"error": "missing_authorization", "error_description": "Missing \"Authorization\" in headers."}

[curl] + [Authorization Code Grant] で REST API を実行

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

https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
Resource Owner = ユーザー、User-Agent = ブラウザ、Client = アプリケーション

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

フロー1:Authorization Server (http://127.0.0.1:5000/oauth/authorize?response_type=code&client_id=${client_id}&scope=profile) にアクセス

フロー2:(User-Agent (= ブラウザ) が自動で) Authorization Server のページを表示

今回はパスワードを必要としない実装です

フロー3:Consent にチェックを入れ、Submit を押下

フロー4:(User-Agent (= ブラウザ) が自動で) Authorization code を表示

赤線で囲った文字列が 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 ライブラリを利用して OAuth 2.0 のフローのクライアントを実装します。

[Python] + [Resource Owner Password Credentials Grant] で REST API を実行

「Python の Requests ライブラリ」 + 「Resource Owner Password Credentials Grant」で、アクセストークンを取得して API を実行します。

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

参考資料・おすすめの書籍

ゼロトラストセキュリティを学べる書籍

暗号系の知識を数式無しで習得できる書籍

Introduce OAuth 2.0
Understand the concepts in OAuth 2.0, the authorization flow, grant types, roles, authentication methods and etc.