コンテンツにスキップ

セキュリティ - 最初の一歩

あるドメインに、バックエンド 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ヘッダーAuthorizationBearerの文字列とトークンを加えた値を送信します。
    • トークンにfoobarが含まれている場合、Authorizationヘッダーの内容は次のようになります: Bearer foobar

FastAPIOAuth2PasswordBearer

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_schemeOAuth2PasswordBearerのインスタンスですが、「呼び出し可能」です。

次のように、呼ぶことができます:

oauth2_scheme(some, parameters)

そのため、Dependsと一緒に使うことができます。

使い方

これでoauth2_schemeDependsで依存関係に渡すことができます。

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.OAuth2fastapi.security.base.SecurityBaseを継承しているからです。

OpenAPIと統合するセキュリティユーティリティ (および自動APIドキュメント) はすべてSecurityBaseを継承しています。それにより、FastAPIはそれらをOpenAPIに統合する方法を知ることができます。

どのように動作するか

リクエストの中にAuthorizationヘッダーを探しに行き、その値がBearerと何らかのトークンを含んでいるかどうかをチェックし、そのトークンをstrとして返します。

もしAuthorizationヘッダーが見つからなかったり、値がBearerトークンを持っていなかったりすると、401 ステータスコードエラー (UNAUTHORIZED) で直接応答します。

トークンが存在するかどうかをチェックしてエラーを返す必要はありません。関数が実行された場合、そのトークンにstrが含まれているか確認できます。

インタラクティブなドキュメントですでに試すことができます:

まだトークンの有効性を検証しているわけではありませんが、これはもう始まっています。

まとめ

つまり、たった3~4行の追加で、すでに何らかの基礎的なセキュリティの形になっています。