コンテンツへスキップ

セキュリティ - はじめの一歩

バックエンドAPIがとあるドメインにあると想像してみましょう。

そして、別のドメインか、同じドメインの異なるパス (またはモバイルアプリケーション) にフロントエンドがあるとします。

そして、ユーザー名パスワードを使って、フロントエンドがバックエンドと認証する手段が欲しいとします。

FastAPIでこれを構築するために、OAuth2を使用できます。

しかし、必要な情報を得るためだけに、長くて完全な仕様書を読む時間を省きましょう。

セキュリティを扱うためにFastAPIが提供するツールを使用しましょう。

概要

まずコードを使ってどのように動作するかを見てみましょう。それから何が起こっているのかを理解するために戻ってきます。

main.py の作成

main.py ファイルに例をコピーします。

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}

ヒント

可能であれば`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: str = Depends(oauth2_scheme)):
    return {"token": token}

実行

情報

pip install "fastapi[standard]" コマンドを実行すると、python-multipart パッケージは FastAPI とともに自動的にインストールされます。

しかし、pip install fastapi コマンドを使用すると、python-multipart パッケージはデフォルトでは含まれません。

手動でインストールするには、仮想環境を作成し、それをアクティブにしてから、次のようにインストールしてください。

$ pip install python-multipart

これは、OAuth2usernamepasswordを送信するために「フォームデータ」を使用するためです。

例を実行します

$ fastapi dev main.py

<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」ボタンがあります。

そして、パス操作の右上隅に小さなロックがあり、それをクリックできます。

それをクリックすると、usernamepassword(およびその他のオプションフィールド)を入力するための小さな認証フォームが表示されます。

Note

フォームに何を入力しても、まだ機能しません。しかし、そこまでたどり着きます。

もちろん、これは最終ユーザー向けのフロントエンドではありませんが、API全体を対話的に文書化するための優れた自動ツールです。

これはフロントエンドチーム (あなた自身である可能性もあります) が使用できます。

サードパーティのアプリケーションやシステムが使用できます。

そして、あなた自身も、同じアプリケーションのデバッグ、チェック、テストに使用できます。

password フロー

では、少し戻って、それらが何なのか理解しましょう。

password "フロー" は、OAuth2で定義されているセキュリティと認証を扱うための方法 ("フロー") の1つです。

OAuth2は、バックエンドまたはAPIがユーザーを認証するサーバーから独立できるように設計されました。

しかし、この場合、同じFastAPIアプリケーションがAPIと認証を処理します。

それでは、簡略化された視点から確認してみましょう。

  • ユーザーはフロントエンドでusernamepasswordを入力し、Enterを押します。
  • フロントエンド(ユーザーのブラウザで実行中)は、そのusernamepasswordをAPI内の特定のURL(tokenUrl="token"で宣言)に送信します。
  • APIはusernamepasswordをチェックし、「トークン」で応答します(これらはまだ実装されていません)。
    • 「トークン」とは、後でこのユーザーを検証するために使用できる、何らかの内容を持つ単なる文字列です。
    • 通常、トークンは一定時間後に期限切れになるように設定されています。
      • そのため、ユーザーは後でどこかの時点で再度ログインする必要があります。
      • そして、トークンが盗まれた場合のリスクは少なくなります。これは(ほとんどの場合)永遠に機能する永続的な鍵とは異なります。
  • フロントエンドは、そのトークンを一時的にどこかに保存します。
  • ユーザーはフロントエンドの別のセクションに移動するためにクリックします。
  • フロントエンドはAPIからさらにデータをフェッチする必要があります。
    • しかし、その特定のエンドポイントには認証が必要です。
    • そこで、APIと認証するために、AuthorizationヘッダーにBearerとトークンを付けた値を送信します。
    • もしトークンにfoobarが含まれている場合、Authorizationヘッダーの内容はBearer foobarになります。

FastAPIOAuth2PasswordBearer

FastAPIは、これらのセキュリティ機能を実装するために、さまざまな抽象度レベルのツールを提供しています。

この例では、OAuth2パスワードフローで、Bearerトークンを使用して使用します。これは、OAuth2PasswordBearerクラスを使って行います。

情報

「ベアラー」トークンは唯一の選択肢ではありません。

しかし、私たちのユースケースには最適です。

OAuth2のエキスパートで、自分のニーズにより適した別のオプションがある理由を正確に知っているのでなければ、ほとんどのユースケースでこれが最適かもしれません。

その場合、FastAPIもそれらを構築するためのツールを提供します。

OAuth2PasswordBearerクラスのインスタンスを作成する際、tokenUrlパラメータを渡します。このパラメータには、クライアント(ユーザーのブラウザで実行されているフロントエンド)がトークンを取得するためにusernamepasswordを送信する際に使用するURLが含まれます。

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}

ヒント

可能であれば`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: str = Depends(oauth2_scheme)):
    return {"token": token}

ヒント

ここで tokenUrl="token" は、まだ作成していない相対URL token を指します。相対URLであるため、./token と同等です。

相対URLを使用しているため、APIがhttps://example.com/にある場合、https://example.com/tokenを参照します。しかし、APIがhttps://example.com/api/v1/にある場合、https://example.com/api/v1/tokenを参照します。

相対URLを使用することは、プロキシの背後のような高度なユースケースでもアプリケーションが動作し続けるために重要です。

このパラメータは、そのエンドポイント / *パス操作*を作成するのではなく、URL /token がクライアントがトークンを取得するために使用すべきものであることを宣言します。その情報はOpenAPIで使用され、インタラクティブAPIドキュメントシステムでも使用されます。

実際のパス操作もすぐに作成します。

情報

もしあなたが非常に厳格な「Pythonista」であれば、パラメータ名がtoken_urlではなくtokenUrlであるスタイルを好まないかもしれません。

それは、OpenAPI仕様と同じ名前を使用しているためです。そのため、これらのセキュリティスキームのいずれかについてさらに調査する必要がある場合は、コピーアンドペーストして詳細情報を見つけることができます。

oauth2_scheme変数はOAuth2PasswordBearerのインスタンスですが、「呼び出し可能」でもあります。

次のように呼び出すことができます。

oauth2_scheme(some, parameters)

したがって、Dependsで使用できます。

使用法

これで、そのoauth2_schemeDependsで依存関係として渡すことができます。

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}

ヒント

可能であれば`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: str = Depends(oauth2_scheme)):
    return {"token": token}

この依存関係は、パス操作関数のパラメータtokenに割り当てられるstrを提供します。

FastAPI は、この依存関係を使用して OpenAPI スキーマ(および自動 API ドキュメント)で「セキュリティスキーム」を定義できることを認識します。

技術的な詳細

FastAPI は、依存関係で宣言されたクラス OAuth2PasswordBearerfastapi.security.base.SecurityBase を継承する fastapi.security.oauth2.OAuth2 を継承しているため、OpenAPI でセキュリティスキームを定義するために使用できることを認識します。

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

動作

リクエストでAuthorizationヘッダーを探し、値がBearerと何らかのトークンであるかを確認し、トークンをstrとして返します。

もしAuthorizationヘッダーが見つからない場合、または値がBearerトークンを含んでいない場合、直接401ステータスコードエラー(UNAUTHORIZED)で応答します。

エラーを返すためにトークンが存在するかどうかを確認する必要さえありません。関数が実行されれば、そのトークンにはstrが確実にあると断言できます。

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

トークンの有効性はまだ検証していませんが、これはすでに始まりです。

まとめ

したがって、わずか3〜4行の追加コードで、すでに何らかの原始的な形のセキュリティを手に入れることができます。