セキュリティ - 最初の一歩¶
あるドメインに、バックエンド APIを持っているとしましょう。
そして、別のドメインか同じドメインの違うパス(またはモバイルアプリケーションの中)に フロントエンドを持っています。
さらに、フロントエンドがユーザー名とパスワードを使って、バックエンドで認証する方法を用意したいです。
FastAPIでは、これをOAuth2を使用して構築できます。
ですが、ちょっとした必要な情報を探すために、長い仕様のすべてを読む必要はありません。
FastAPIが提供するツールを使って、セキュリティを制御してみましょう。
どう見えるか¶
まずはこのコードを使って、どう動くか観察します。その後で、何が起こっているのか理解しましょう。
main.py
を作成¶
main.py
に、下記の例をコピーします:
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return {"token": token}
🤓 Other versions and variants
from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from typing_extensions import Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
実行¶
情報
まずpython-multipart
をインストールします。
例えば、pip install python-multipart
。
これは、OAuth2が ユーザー名
や パスワード
を送信するために、「フォームデータ」を使うからです。
例を実行します:
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
確認¶
次のインタラクティブなドキュメントにアクセスしてください: http://127.0.0.1:8000/docs。
下記のように見えるでしょう:
Authorizeボタン!
すでにピカピカの新しい「Authorize」ボタンがあります。
そして、あなたのpath operationには、右上にクリックできる小さな鍵アイコンがあります。
それをクリックすると、ユーザー名
とパスワード
(およびその他のオプションフィールド) を入力する小さな認証フォームが表示されます:
備考
フォームに何を入力しても、まだうまくいきません。ですが、これから動くようになります。
もちろんエンドユーザーのためのフロントエンドではありません。しかし、すべてのAPIをインタラクティブにドキュメント化するための素晴らしい自動ツールです。
フロントエンドチームはこれを利用できます (また、あなたも利用できます) 。
サードパーティのアプリケーションやシステムでも使用可能です。
また、同じアプリケーションのデバッグ、チェック、テストのためにも利用できます。
パスワード
フロー¶
では、少し話を戻して、どうなっているか理解しましょう。
パスワード
の「フロー」は、OAuth2で定義されているセキュリティと認証を扱う方法 (「フロー」) の1つです。
OAuth2は、バックエンドやAPIがユーザーを認証するサーバーから独立したものとして設計されていました。
しかし、この場合、同じFastAPIアプリケーションがAPIと認証を処理します。
そこで、簡略化した箇所から見直してみましょう:
- ユーザーはフロントエンドで
ユーザー名
とパスワード
を入力し、Enter
を押します。 - フロントエンド (ユーザーのブラウザで実行中) は、
ユーザー名
とパスワード
をAPIの特定のURL (tokenUrl="token"
で宣言された) に送信します。 - APIは
ユーザー名
とパスワード
をチェックし、「トークン」を返却します (まだ実装していません)。- 「トークン」はただの文字列であり、あとでこのユーザーを検証するために使用します。
- 通常、トークンは時間が経つと期限切れになるように設定されています。
- トークンが期限切れの場合は、再度ログインする必要があります。
- トークンが盗まれたとしても、リスクは低いです。永久キーのように永遠に機能するものではありません(ほとんどの場合)。
- フロントエンドはそのトークンを一時的にどこかに保存します。
- ユーザーがフロントエンドでクリックして、フロントエンドのWebアプリの別のセクションに移動します。
- フロントエンドはAPIからさらにデータを取得する必要があります。
- しかし、特定のエンドポイントの認証が必要です。
- したがって、APIで認証するため、HTTPヘッダー
Authorization
にBearer
の文字列とトークンを加えた値を送信します。 - トークンに
foobar
が含まれている場合、Authorization
ヘッダーの内容は次のようになります:Bearer foobar
。
FastAPIのOAuth2PasswordBearer
¶
FastAPIは、これらのセキュリティ機能を実装するために、抽象度の異なる複数のツールを提供しています。
この例では、Bearerトークンを使用してOAuth2をパスワードフローで使用します。これにはOAuth2PasswordBearer
クラスを使用します。
情報
「bearer」トークンが、唯一の選択肢ではありません。
しかし、私たちのユースケースには最適です。
あなたがOAuth2の専門家で、あなたのニーズに適した別のオプションがある理由を正確に知っている場合を除き、ほとんどのユースケースに最適かもしれません。
その場合、FastAPIはそれを構築するためのツールも提供します。
OAuth2PasswordBearer
クラスのインスタンスを作成する時に、パラメーターtokenUrl
を渡します。このパラメーターには、クライアント (ユーザーのブラウザで動作するフロントエンド) がトークンを取得するためにユーザー名
とパスワード
を送信するURLを指定します。
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return {"token": token}
🤓 Other versions and variants
from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from typing_extensions import Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
豆知識
ここで、tokenUrl="token"
は、まだ作成していない相対URLtoken
を指します。相対URLなので、./token
と同じです。
相対URLを使っているので、APIがhttps://example.com/
にある場合、https://example.com/token
を参照します。しかし、APIがhttps://example.com/api/v1/
にある場合はhttps://example.com/api/v1/token
を参照することになります。
相対 URL を使うことは、プロキシと接続のような高度なユースケースでもアプリケーションを動作させ続けるために重要です。
このパラメーターはエンドポイント/ path operationを作成しません。しかし、URL/token
はクライアントがトークンを取得するために使用するものであると宣言します。この情報は OpenAPI やインタラクティブな API ドキュメントシステムで使われます。
実際のpath operationもすぐに作ります。
情報
非常に厳格な「Pythonista」であれば、パラメーター名のスタイルがtoken_url
ではなくtokenUrl
であることを気に入らないかもしれません。
それはOpenAPI仕様と同じ名前を使用しているからです。そのため、これらのセキュリティスキームについてもっと調べる必要がある場合は、それをコピーして貼り付ければ、それについての詳細な情報を見つけることができます。
変数oauth2_scheme
はOAuth2PasswordBearer
のインスタンスですが、「呼び出し可能」です。
次のように、呼ぶことができます:
oauth2_scheme(some, parameters)
そのため、Depends
と一緒に使うことができます。
使い方¶
これでoauth2_scheme
をDepends
で依存関係に渡すことができます。
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
return {"token": token}
🤓 Other versions and variants
from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from typing_extensions import Annotated
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: Annotated[str, Depends(oauth2_scheme)]):
return {"token": token}
この依存関係は、path operation functionのパラメーターtoken
に代入されるstr
を提供します。
FastAPIは、この依存関係を使用してOpenAPIスキーマ (および自動APIドキュメント) で「セキュリティスキーム」を定義できることを知っています。
技術詳細
FastAPIは、OAuth2PasswordBearer
クラス (依存関係で宣言されている) を使用してOpenAPIのセキュリティスキームを定義できることを知っています。これはfastapi.security.oauth2.OAuth2
、fastapi.security.base.SecurityBase
を継承しているからです。
OpenAPIと統合するセキュリティユーティリティ (および自動APIドキュメント) はすべてSecurityBase
を継承しています。それにより、FastAPIはそれらをOpenAPIに統合する方法を知ることができます。
どのように動作するか¶
リクエストの中にAuthorization
ヘッダーを探しに行き、その値がBearer
と何らかのトークンを含んでいるかどうかをチェックし、そのトークンをstr
として返します。
もしAuthorization
ヘッダーが見つからなかったり、値がBearer
トークンを持っていなかったりすると、401 ステータスコードエラー (UNAUTHORIZED
) で直接応答します。
トークンが存在するかどうかをチェックしてエラーを返す必要はありません。関数が実行された場合、そのトークンにstr
が含まれているか確認できます。
インタラクティブなドキュメントですでに試すことができます:
まだトークンの有効性を検証しているわけではありませんが、これはもう始まっています。
まとめ¶
つまり、たった3~4行の追加で、すでに何らかの基礎的なセキュリティの形になっています。