坂本  篤司

坂本 篤司

1636488120

Auth0を使用してFastAPIサーバーを構築して保護する

FastAPIは比較的新しいPythonフレームワークであり、アプリケーションを非常に迅速に作成できます。このフレームワークを使用すると、組み込みモジュールを使用してAPIリクエストデータをシームレスに読み取ることができ、Flaskの軽量な代替手段となります。

この記事では、の機能について説明FastAPIし、基本的なAPIを設定し、Auth0を使用してエンドポイントを保護し、開始するのがいかに簡単かを学びます。

始める前に、以下のビデオを再生して、このブログ投稿の内容をビデオ形式でチェックすることもできます:👇

前提条件

FastAPIを使用してビルドを開始する前に、Pythonと無料のAuth0アカウントが必要です。ここでサインアップできます。3.8.2

そのPythonバージョンをインストールし、Auth0アカウントを取得している場合は、新しいFastAPIアプリケーションを作成できます。まず、開発する新しいディレクトリを作成します。この例では、というディレクトリと;というサブフォルダを作成します。このサブフォルダーは、コードが存在する場所です。fastapi-exampleapplication

フォルダで、次のコマンドを使用して仮想環境を作成します。fastapi-example

python3 -m venv .env

これにより仮想環境が作成され、依存関係が残りのコンピューターライブラリから分離されます。つまり、他のPythonプロジェクトに影響を与える可能性のある、ライブラリと依存関係でグローバル名前空間を汚染することはありません。

仮想環境を作成したら、それをアクティブ化する必要があります。Unixベースのオペレーティングシステムの場合、コマンドは次のとおりです。

source .env/bin/activate

別のオペレーティングシステムを使用している場合は、このドキュメントページで環境をアクティブ化する方法のリストを見つけることができます。仮想環境をアクティブ化した後、使用するパッケージをインストールできます:FastAPIuvicorn server、pyjwt、およびupdate pip

pip install -U pip
pip install fastapi uvicorn 'pyjwt[crypto]'

FastAPIの使用を開始する

すべてのライブラリがインストールされたので、フォルダ内にファイルを作成できます。それはあなたのAPIコードが住む場所です。の内容は次のようになります。main.pyapplicationmain.py

"""main.py
Python FastAPI Auth0 integration example
"""
 
from fastapi import FastAPI
# Creates app instance
app = FastAPI()
 
 
@app.get("/api/public")
def public():
    """No access token required to access this route"""

    result = {
        "status": "success",
        "msg": ("Hello from a public endpoint! You don't need to be "
                "authenticated to see this.")
    }
    return result

これを分解しましょう:

  • まず、FastAPIライブラリをインポートします
  • 次に、オブジェクトをインスタンス化してアプリを作成しますFastAPI()
  • その後、リクエストを処理するルートを定義するために使用します@app.getGET

最後に、と呼ばれるパス操作関数があります。これは、そのルートが呼び出されるたびに実行される関数であり、ウェルカムメッセージを含む辞書を返します。public()

最初のエンドポイントコードを取得したので、サーバーを起動して実行するには、プロジェクトのルートディレクトリで次のコマンドを実行します。

uvicorn application.main:app --reload

サーバーが実行されている状態で、次の画像に示すように、最初のエンドポイント用に自動生成されたドキュメントを表示するために移動できます。http://127.0.0.1:8000/docs

パブリックエンドポイントを示すFastAPIドキュメントページ

または、を使用して、新しいターミナルウィンドウで最初のリクエストを行うこともできますcURL。古いバージョンのオペレーティングシステムを使用しているWindowsユーザーの場合は、次のコマンドを実行する前にcurlインストールする必要があることに注意してください。

curl -X 'GET' \
  --url http://127.0.0.1:8000/api/public

そして、これと同じように行ったリクエストの結果として、JSONが表示されるはずです。

{
  "status": "success",
  "msg": "Hello from a public endpoint! You don't need to be authenticated to see this."
}

簡単にするcURLために、この投稿の残りの部分ではを使用します。

プライベートエンドポイントを作成する

ベースAPIサーバーがセットアップされたので、ファイルにもう1つのエンドポイントを追加します。このアプリケーションでは、すべての人が利用できるルートと、Auth0から取得するアクセストークンを使用して自分だけがアクセスできるルートがあります。main.pyGET /api/publicGET /api/private

次に、ファイルを更新する必要があります。インポートセクションに変更する必要があるものは次のとおりです。main.py

  • まず、あなたがインポートする必要があるDependsからfastapiFastAPI依存性注入システムのモジュールと、
  • 次に、モジュールHTTPBearerからクラスをインポートする必要があります。これは、ベアラートークンを使用した承認ヘッダー用の組み込みのセキュリティスキームです。fastapi.security
  • に基づいて承認スキームを作成する必要がありますHTTPBearer。これはBearer、プライベートエンドポイントに対して行われる各リクエストでトークンを含む承認ヘッダーの存在を保証するために使用されます。

トークン知らせるトークンのベアラがいることをAPIにアクセスするAPIを承認し、承認の際に付与された範囲で指定された特定のアクションを実行されています。

インポートを更新する以外に、プライベートエンドポイントを実装する必要があります。エンドポイントはまた、受け入れるの要求を、ここでのコードは何である今のようになっています。/api/privateGETmain.py

"""main.py
Python FastAPI Auth0 integration example
"""
 
from fastapi import Depends, FastAPI  # 👈 new imports
from fastapi.security import HTTPBearer  # 👈 new imports


# Scheme for the Authorization header
token_auth_scheme = HTTPBearer()  # 👈 new code
 
# Creates app instance
app = FastAPI()

@app.get("/api/public")
def public():
    """No access token required to access this route"""
 
    result = {
        "status": "success",
        "msg": ("Hello from a public endpoint! You don't need to be "
                "authenticated to see this.")
    }
    return result


# new code 👇
@app.get("/api/private")
def private(token: str = Depends(token_auth_scheme)):
    """A valid access token is required to access this route"""
 
    result = token.credentials

    return result

Dependsこのクラスは、特定のエンドポイントは、関数、クラス、またはインスタンスに対して受信する各要求を評価する責任があります。この場合、HTTPBearerベアラートークンを使用して承認ヘッダーのリクエストをチェックするスキームに対してリクエストを評価します。

FastAPI 依存性注入がどのように機能するかについての詳細は、そのドキュメントに記載されています

これで、プライベートエンドポイントが受信したトークンを返します。トークンが提供されていない場合は、403 Forbiddenあなたがいることを示す詳細を含むステータスコードが返されます"Not authenticated"--reloadサーバーの実行中にフラグを使用したため、コマンドを再実行する必要はありません。uvicornファイルを保存するたびに、変更を取得してサーバーを更新します。次に、エンドポイントにリクエストを送信して、その動作を確認します。まず、認証ヘッダー渡さずにリクエスト作成しましょう。GET /api/private

curl -X 'GET' \
  --url 'http://127.0.0.1:8000/api/private'
# {"detail": "Not authenticated"}

そして今、認証ヘッダーを使用してリクエストを行ったが、トークン値としてランダムな文字列を使用した場合、結果として同じランダムな値が表示されるはずです。

curl -X 'GET' \
  --url 'http://127.0.0.1:8000/api/private' \
  --header 'Authorization: Bearer FastAPI is awesome'
# "FastAPI is awesome"

ご覧のとおり、承認ヘッダーの値として任意の文字列を受け入れるため、エンドポイントは保護されていません。承認ヘッダーを受け取るだけは不十分です。また、誰かがエンドポイントにアクセスできるようにするには、ベアラートークンの値を確認する必要があります。その動作を修正しましょう。

Auth0をAPIに設定する

エンドポイントでトークンを検証する準備が整う前に、Auth0でAPI設定する必要があります。このAPIを設定すると、Auth0に必要ないくつかの情報(オーディエンス、クライアントID、クライアントシークレット)にアクセスできます。

また、サーバー内からその情報にアクセスできる必要があります。そこで、構成ファイルが役立ちます。.configプロジェクトのルートで呼び出される構成ファイルを作成する必要があります。これは、.configファイルが以下のように見えるはずです。それに応じて値を更新することを忘れないでください。

# .config
 
[AUTH0]
DOMAIN = your.domain.auth0.com
API_AUDIENCE = your.api.audience
ALGORITHMS = RS256
ISSUER = https://your.domain.auth0.com/

この構成は、トークン検証段階でAuth0構成設定をチェックするパズルの最初のピースです。従うべきもう1つの良いルールは、環境変数を含む構成ファイルをソースコードにコミットしないことです。これを防ぐに.gitignoreは、プロジェクトのルートにファイルを作成し、その.configファイルをエントリとして追加する必要があります。

# .gitignore
.config

JSON Web Token(JWT)検証を追加する

これでFastAPIサーバーにルートができましたが、まだ保護されていません。リクエストに認証ヘッダーがあるかどうかのみをチェックします。つまり、プロセスのステップが欠落しています。アクセストークンを検証する必要があります。これを行うには、Auth0のアクセストークンがJWTであるため、JWTを検証するためのすべての手順を実行するオブジェクトを作成する必要があります。GET /api/private

責任をルーティング定義から分離するには、フォルダー内に呼び出される新しいファイルを作成して、アクセストークンの検証や構成情報の読み取りなど、すべてのユーティリティコードを保持する必要があります。utils.pyapplication

Pythonosライブラリ、PyJWTおよびconfigparserライブラリをインポートすることから始めます。OSライブラリを使用すると、環境変数にアクセスできます。JWTライブラリは、JWTをチェックおよび検証するための関数を提供します。ConfigParser名前の由来のライブラリのクラスは、Pythonが.config以前に作成したファイルにある構成設定を読み取る方法を提供します。そして、インポート後に最初に持っているのは、と呼ばれる関数です。これを以下に示します。set_up()

"""utils.py
"""

import os
import jwt
from configparser import ConfigParser

 
def set_up():
    """Sets up configuration for the app"""

    env = os.getenv("ENV", ".config")

    if env == ".config":
        config = ConfigParser()
        config.read(".config")
        config = config["AUTH0"]
    else:
        config = {
            "DOMAIN": os.getenv("DOMAIN", "your.domain.com"),
            "API_AUDIENCE": os.getenv("API_AUDIENCE", "your.audience.com"),
            "ISSUER": os.getenv("ISSUER", "https://your.domain.com/"),
            "ALGORITHMS": os.getenv("ALGORITHMS", "RS256"),
        }
    return config

この関数は、ファイルを読み取り、辞書のように機能する構成オブジェクトを作成する役割を果たします。このサンプルコードは環境変数でも実行できるように準備されているため、関数はデフォルトでファイルを読み取ろうとします。この動作は、環境変数を他の値に設定することで変更できます。その場合、上記の句で確認できるすべての環境変数を読み取ることで辞書が作成されます。set_up().configset_up().configENVelse

パズルの次のピースは、魔法が起こる場所です。VerifyTokenJWTトークンの検証を処理するクラスを作成します。

# paste the code 👇 after the set_up() function in the utils.py file
class VerifyToken():
    """Does all the token verification using PyJWT"""

    def __init__(self, token):
        self.token = token
        self.config = set_up()

        # This gets the JWKS from a given URL and does processing so you can
        # use any of the keys available
        jwks_url = f'https://{self.config["DOMAIN"]}/.well-known/jwks.json'
        self.jwks_client = jwt.PyJWKClient(jwks_url)

    def verify(self):
        # This gets the 'kid' from the passed token
        try:
            self.signing_key = self.jwks_client.get_signing_key_from_jwt(
                self.token
            ).key
        except jwt.exceptions.PyJWKClientError as error:
            return {"status": "error", "msg": error.__str__()}
        except jwt.exceptions.DecodeError as error:
            return {"status": "error", "msg": error.__str__()}

        try:
            payload = jwt.decode(
                self.token,
                self.signing_key,
                algorithms=self.config["ALGORITHMS"],
                audience=self.config["API_AUDIENCE"],
                issuer=self.config["ISSUER"],
            )
        except Exception as e:
            return {"status": "error", "message": str(e)}

        return payload

ここでの手順を理解するために、このクラスを分解してみましょう。

  1. まず、次の方法があります。 __init__()
    1. このメソッドはtokenVerifyTokenクラスが必要とするパラメーターを指定する役割を果たします。
    2. また、関数を実行して、クラスに必要な構成を構築します。set_up()
    3. そして最後に、パッケージJWKSPyJWKClientfromを使用してファイルのパスを設定しPyJWTます。A JSONのWebキーセット、または略してJWKSは、トークンの署名を検証し、それが有効なトークンであることを保証するために必要な情報が含まれています。Auth0はOAuth2.0を実装しているため、トークンとそのプロパティの検証に使用される追加のメタデータを呼び出して取得できる「既知の」エンドポイントがあります。
  2. 次に、次の方法があります。 verify()
    1. このメソッドは、キーID(kidトークンヘッダーに存在するクレーム)を使用して、JWKSから使用されるキーを取得してトークンの署名を検証します。考えられるエラーのいずれかによってこのステップが失敗した場合、エラーメッセージが返されます。
    2. 次に、このメソッドは、これまでに収集された情報を使用してJWTをデコードしようとします。エラーの場合は、エラーメッセージを返します。成功すると、トークンペイロードが返されます。

前のセクションのプライベートエンドポイントでこのコードを使用する方法を見てみましょう。

Auth0アクセストークンを検証する

最後のパズルのピースは、ファイルに作成したクラスをインポートして、エンドポイントで使用することです。変更する必要があるものは次のとおりです。utils.pyGET /api/private

  1. importsセクションを更新して、のimport句を追加してVerifyTokenから、エンドポイントに移動します。また、エラーが発生した場合に詳細な応答を提供できるように、からResponseクラスとstatusオブジェクトをインポートする必要fastapiがあります。
  2. 次に、トークンをVerifyTokenクラスに渡し、メソッドの結果がエラーであるかどうかを確認して、エンドポイントを調整する必要があります。verify()

上記のすべての変更を加えたファイルは次のようになります。main.py

"""main.py
Python FastAPI Auth0 integration example
"""
 
from fastapi import Depends, FastAPI, Response, status  # 👈 new imports
from fastapi.security import HTTPBearer
 
from .utils import VerifyToken  # 👈 new import

# Scheme for the Authorization header
token_auth_scheme = HTTPBearer()
 
# Creates app instance
app = FastAPI()
 
 
@app.get("/api/public")
def public():
   """No access token required to access this route"""
 
   result = {
       "status": "success",
       "msg": ("Hello from a public endpoint! You don't need to be "
               "authenticated to see this.")
   }
   return result
 
 
@app.get("/api/private")
def private(response: Response, token: str = Depends(token_auth_scheme)):  # 👈 updated code
    """A valid access token is required to access this route"""
 
    result = VerifyToken(token.credentials).verify()  # 👈 updated code

    # 👇 new code
    if result.get("status"):
       response.status_code = status.HTTP_400_BAD_REQUEST
       return result
    # 👆 new code
 
    return result

この更新により、保護されたエンドポイントが適切に設定され、必要なアクセストークンのすべての検証手順が実行されます。🎉

--reload構成がロードされていることを確認する必要があるため、フラグを使用してサーバーを起動した場合でも、uvicornプロセスを終了してからサーバーを再起動することをお勧めします。これにより、.configファイルまたは環境変数の構成パラメーターを使用してAPIの適切な機能が保証されます。

FastAPIサーバー内の保護されたエンドポイントにリクエストを送信する前に、Auth0からのアクセストークンが必要です。TestAPIのタブにあるAuth0ダッシュボードからコピーして取得できます。

POSTAuth0のエンドポイントへのcurlリクエストを使用してアクセストークンを取得することもできます。このリクエストは、Auth0ダッシュボードのAPIのタブからコピーできます。カールリクエストは次のようになります。必要に応じて値を入力することを忘れないでください。oauth/tokenTest

curl -X 'POST' \
--url 'https://<YOUR DOMAIN HERE>/oauth/token' \
 --header 'content-type: application/x-www-form-urlencoded' \
 --data grant_type=client_credentials \
 --data 'client_id=<YOUR CLIENT ID HERE>' \
 --data client_secret=<YOUR CLIENT SECRET HERE> \
 --data audience=<YOUR AUDIENCE HERE>

コマンドラインに、次のようなベアラートークンを含む応答が表示されます。

{
    "access_token": "<YOUR_BEARER_TOKEN>",
    "expires_in": 86400,
    "token_type": "Bearer"
}

これで、このアクセストークンを使用してプライベートエンドポイントにアクセスできます。

curl -X 'GET' \
--url 'http://127.0.0.1:8000/api/private' \
--header  'Authorization: Bearer <YOUR_BEARER_TOKEN>'

リクエストが成功すると、サーバーはアクセストークンのペイロードを送り返します。

{
    "iss": "https://<YOUR_DOMAIN>/",
    "sub": "iojadoijawdioWDasdijasoid@clients",
    "aud": "http://<YOUR_AUDIENCE>",
    "iat": 1630341660,
    "exp": 1630428060,
    "azp": "ADKASDawdopjaodjwopdAWDdsd",
    "gty": "client-credentials"
}

検証が失敗した場合は、何が悪かったのかを詳細に確認する必要があることに注意してください。

これで、プライベートエンドポイントの保護とその保護のテストが完了しました。

要約

あなたはこのブログ投稿でかなり多くのことを学びました。まず、FastAPI2つのエンドポイント(1つはパブリック、もう1つはプライベート)を実装することで、の基本を学びました。これらのエンドポイントの両方にリクエストを送信するのがいかに簡単であるかを見てきました。検証クラスを作成し、PyJWTがAuth0アクセストークンの検証にどのように役立つかを確認し、JWKSとは何かを学びました。

Auth0ダッシュボードでAPIを作成するプロセスを実行しました。また、FastAPIが提供する依存性注入システムを活用して統合の実装を支援することにより、エンドポイントの1つを保護する方法も学びました。そして、あなたはこれらすべてを非常に迅速に行いました。

つまり、FastAPIエンドポイントを保護するためにAuth0を使用する方法だけでなく、を起動して実行するのがいかに簡単であるかを学びました。

このGitHubリポジトリには、今日作成したサンプルアプリケーションの完全なコードが含まれています。ご不明な点がございましたら、コミュニティフォーラムのスレッドでこのブログ投稿をお尋ねください。

リンク: https://auth0.com/blog/build-and-secure-fastapi-server-with-auth0/

#auth0 #security #fastapi 

What is GEEK

Buddha Community

Auth0を使用してFastAPIサーバーを構築して保護する
坂本  篤司

坂本 篤司

1636488120

Auth0を使用してFastAPIサーバーを構築して保護する

FastAPIは比較的新しいPythonフレームワークであり、アプリケーションを非常に迅速に作成できます。このフレームワークを使用すると、組み込みモジュールを使用してAPIリクエストデータをシームレスに読み取ることができ、Flaskの軽量な代替手段となります。

この記事では、の機能について説明FastAPIし、基本的なAPIを設定し、Auth0を使用してエンドポイントを保護し、開始するのがいかに簡単かを学びます。

始める前に、以下のビデオを再生して、このブログ投稿の内容をビデオ形式でチェックすることもできます:👇

前提条件

FastAPIを使用してビルドを開始する前に、Pythonと無料のAuth0アカウントが必要です。ここでサインアップできます。3.8.2

そのPythonバージョンをインストールし、Auth0アカウントを取得している場合は、新しいFastAPIアプリケーションを作成できます。まず、開発する新しいディレクトリを作成します。この例では、というディレクトリと;というサブフォルダを作成します。このサブフォルダーは、コードが存在する場所です。fastapi-exampleapplication

フォルダで、次のコマンドを使用して仮想環境を作成します。fastapi-example

python3 -m venv .env

これにより仮想環境が作成され、依存関係が残りのコンピューターライブラリから分離されます。つまり、他のPythonプロジェクトに影響を与える可能性のある、ライブラリと依存関係でグローバル名前空間を汚染することはありません。

仮想環境を作成したら、それをアクティブ化する必要があります。Unixベースのオペレーティングシステムの場合、コマンドは次のとおりです。

source .env/bin/activate

別のオペレーティングシステムを使用している場合は、このドキュメントページで環境をアクティブ化する方法のリストを見つけることができます。仮想環境をアクティブ化した後、使用するパッケージをインストールできます:FastAPIuvicorn server、pyjwt、およびupdate pip

pip install -U pip
pip install fastapi uvicorn 'pyjwt[crypto]'

FastAPIの使用を開始する

すべてのライブラリがインストールされたので、フォルダ内にファイルを作成できます。それはあなたのAPIコードが住む場所です。の内容は次のようになります。main.pyapplicationmain.py

"""main.py
Python FastAPI Auth0 integration example
"""
 
from fastapi import FastAPI
# Creates app instance
app = FastAPI()
 
 
@app.get("/api/public")
def public():
    """No access token required to access this route"""

    result = {
        "status": "success",
        "msg": ("Hello from a public endpoint! You don't need to be "
                "authenticated to see this.")
    }
    return result

これを分解しましょう:

  • まず、FastAPIライブラリをインポートします
  • 次に、オブジェクトをインスタンス化してアプリを作成しますFastAPI()
  • その後、リクエストを処理するルートを定義するために使用します@app.getGET

最後に、と呼ばれるパス操作関数があります。これは、そのルートが呼び出されるたびに実行される関数であり、ウェルカムメッセージを含む辞書を返します。public()

最初のエンドポイントコードを取得したので、サーバーを起動して実行するには、プロジェクトのルートディレクトリで次のコマンドを実行します。

uvicorn application.main:app --reload

サーバーが実行されている状態で、次の画像に示すように、最初のエンドポイント用に自動生成されたドキュメントを表示するために移動できます。http://127.0.0.1:8000/docs

パブリックエンドポイントを示すFastAPIドキュメントページ

または、を使用して、新しいターミナルウィンドウで最初のリクエストを行うこともできますcURL。古いバージョンのオペレーティングシステムを使用しているWindowsユーザーの場合は、次のコマンドを実行する前にcurlインストールする必要があることに注意してください。

curl -X 'GET' \
  --url http://127.0.0.1:8000/api/public

そして、これと同じように行ったリクエストの結果として、JSONが表示されるはずです。

{
  "status": "success",
  "msg": "Hello from a public endpoint! You don't need to be authenticated to see this."
}

簡単にするcURLために、この投稿の残りの部分ではを使用します。

プライベートエンドポイントを作成する

ベースAPIサーバーがセットアップされたので、ファイルにもう1つのエンドポイントを追加します。このアプリケーションでは、すべての人が利用できるルートと、Auth0から取得するアクセストークンを使用して自分だけがアクセスできるルートがあります。main.pyGET /api/publicGET /api/private

次に、ファイルを更新する必要があります。インポートセクションに変更する必要があるものは次のとおりです。main.py

  • まず、あなたがインポートする必要があるDependsからfastapiFastAPI依存性注入システムのモジュールと、
  • 次に、モジュールHTTPBearerからクラスをインポートする必要があります。これは、ベアラートークンを使用した承認ヘッダー用の組み込みのセキュリティスキームです。fastapi.security
  • に基づいて承認スキームを作成する必要がありますHTTPBearer。これはBearer、プライベートエンドポイントに対して行われる各リクエストでトークンを含む承認ヘッダーの存在を保証するために使用されます。

トークン知らせるトークンのベアラがいることをAPIにアクセスするAPIを承認し、承認の際に付与された範囲で指定された特定のアクションを実行されています。

インポートを更新する以外に、プライベートエンドポイントを実装する必要があります。エンドポイントはまた、受け入れるの要求を、ここでのコードは何である今のようになっています。/api/privateGETmain.py

"""main.py
Python FastAPI Auth0 integration example
"""
 
from fastapi import Depends, FastAPI  # 👈 new imports
from fastapi.security import HTTPBearer  # 👈 new imports


# Scheme for the Authorization header
token_auth_scheme = HTTPBearer()  # 👈 new code
 
# Creates app instance
app = FastAPI()

@app.get("/api/public")
def public():
    """No access token required to access this route"""
 
    result = {
        "status": "success",
        "msg": ("Hello from a public endpoint! You don't need to be "
                "authenticated to see this.")
    }
    return result


# new code 👇
@app.get("/api/private")
def private(token: str = Depends(token_auth_scheme)):
    """A valid access token is required to access this route"""
 
    result = token.credentials

    return result

Dependsこのクラスは、特定のエンドポイントは、関数、クラス、またはインスタンスに対して受信する各要求を評価する責任があります。この場合、HTTPBearerベアラートークンを使用して承認ヘッダーのリクエストをチェックするスキームに対してリクエストを評価します。

FastAPI 依存性注入がどのように機能するかについての詳細は、そのドキュメントに記載されています

これで、プライベートエンドポイントが受信したトークンを返します。トークンが提供されていない場合は、403 Forbiddenあなたがいることを示す詳細を含むステータスコードが返されます"Not authenticated"--reloadサーバーの実行中にフラグを使用したため、コマンドを再実行する必要はありません。uvicornファイルを保存するたびに、変更を取得してサーバーを更新します。次に、エンドポイントにリクエストを送信して、その動作を確認します。まず、認証ヘッダー渡さずにリクエスト作成しましょう。GET /api/private

curl -X 'GET' \
  --url 'http://127.0.0.1:8000/api/private'
# {"detail": "Not authenticated"}

そして今、認証ヘッダーを使用してリクエストを行ったが、トークン値としてランダムな文字列を使用した場合、結果として同じランダムな値が表示されるはずです。

curl -X 'GET' \
  --url 'http://127.0.0.1:8000/api/private' \
  --header 'Authorization: Bearer FastAPI is awesome'
# "FastAPI is awesome"

ご覧のとおり、承認ヘッダーの値として任意の文字列を受け入れるため、エンドポイントは保護されていません。承認ヘッダーを受け取るだけは不十分です。また、誰かがエンドポイントにアクセスできるようにするには、ベアラートークンの値を確認する必要があります。その動作を修正しましょう。

Auth0をAPIに設定する

エンドポイントでトークンを検証する準備が整う前に、Auth0でAPI設定する必要があります。このAPIを設定すると、Auth0に必要ないくつかの情報(オーディエンス、クライアントID、クライアントシークレット)にアクセスできます。

また、サーバー内からその情報にアクセスできる必要があります。そこで、構成ファイルが役立ちます。.configプロジェクトのルートで呼び出される構成ファイルを作成する必要があります。これは、.configファイルが以下のように見えるはずです。それに応じて値を更新することを忘れないでください。

# .config
 
[AUTH0]
DOMAIN = your.domain.auth0.com
API_AUDIENCE = your.api.audience
ALGORITHMS = RS256
ISSUER = https://your.domain.auth0.com/

この構成は、トークン検証段階でAuth0構成設定をチェックするパズルの最初のピースです。従うべきもう1つの良いルールは、環境変数を含む構成ファイルをソースコードにコミットしないことです。これを防ぐに.gitignoreは、プロジェクトのルートにファイルを作成し、その.configファイルをエントリとして追加する必要があります。

# .gitignore
.config

JSON Web Token(JWT)検証を追加する

これでFastAPIサーバーにルートができましたが、まだ保護されていません。リクエストに認証ヘッダーがあるかどうかのみをチェックします。つまり、プロセスのステップが欠落しています。アクセストークンを検証する必要があります。これを行うには、Auth0のアクセストークンがJWTであるため、JWTを検証するためのすべての手順を実行するオブジェクトを作成する必要があります。GET /api/private

責任をルーティング定義から分離するには、フォルダー内に呼び出される新しいファイルを作成して、アクセストークンの検証や構成情報の読み取りなど、すべてのユーティリティコードを保持する必要があります。utils.pyapplication

Pythonosライブラリ、PyJWTおよびconfigparserライブラリをインポートすることから始めます。OSライブラリを使用すると、環境変数にアクセスできます。JWTライブラリは、JWTをチェックおよび検証するための関数を提供します。ConfigParser名前の由来のライブラリのクラスは、Pythonが.config以前に作成したファイルにある構成設定を読み取る方法を提供します。そして、インポート後に最初に持っているのは、と呼ばれる関数です。これを以下に示します。set_up()

"""utils.py
"""

import os
import jwt
from configparser import ConfigParser

 
def set_up():
    """Sets up configuration for the app"""

    env = os.getenv("ENV", ".config")

    if env == ".config":
        config = ConfigParser()
        config.read(".config")
        config = config["AUTH0"]
    else:
        config = {
            "DOMAIN": os.getenv("DOMAIN", "your.domain.com"),
            "API_AUDIENCE": os.getenv("API_AUDIENCE", "your.audience.com"),
            "ISSUER": os.getenv("ISSUER", "https://your.domain.com/"),
            "ALGORITHMS": os.getenv("ALGORITHMS", "RS256"),
        }
    return config

この関数は、ファイルを読み取り、辞書のように機能する構成オブジェクトを作成する役割を果たします。このサンプルコードは環境変数でも実行できるように準備されているため、関数はデフォルトでファイルを読み取ろうとします。この動作は、環境変数を他の値に設定することで変更できます。その場合、上記の句で確認できるすべての環境変数を読み取ることで辞書が作成されます。set_up().configset_up().configENVelse

パズルの次のピースは、魔法が起こる場所です。VerifyTokenJWTトークンの検証を処理するクラスを作成します。

# paste the code 👇 after the set_up() function in the utils.py file
class VerifyToken():
    """Does all the token verification using PyJWT"""

    def __init__(self, token):
        self.token = token
        self.config = set_up()

        # This gets the JWKS from a given URL and does processing so you can
        # use any of the keys available
        jwks_url = f'https://{self.config["DOMAIN"]}/.well-known/jwks.json'
        self.jwks_client = jwt.PyJWKClient(jwks_url)

    def verify(self):
        # This gets the 'kid' from the passed token
        try:
            self.signing_key = self.jwks_client.get_signing_key_from_jwt(
                self.token
            ).key
        except jwt.exceptions.PyJWKClientError as error:
            return {"status": "error", "msg": error.__str__()}
        except jwt.exceptions.DecodeError as error:
            return {"status": "error", "msg": error.__str__()}

        try:
            payload = jwt.decode(
                self.token,
                self.signing_key,
                algorithms=self.config["ALGORITHMS"],
                audience=self.config["API_AUDIENCE"],
                issuer=self.config["ISSUER"],
            )
        except Exception as e:
            return {"status": "error", "message": str(e)}

        return payload

ここでの手順を理解するために、このクラスを分解してみましょう。

  1. まず、次の方法があります。 __init__()
    1. このメソッドはtokenVerifyTokenクラスが必要とするパラメーターを指定する役割を果たします。
    2. また、関数を実行して、クラスに必要な構成を構築します。set_up()
    3. そして最後に、パッケージJWKSPyJWKClientfromを使用してファイルのパスを設定しPyJWTます。A JSONのWebキーセット、または略してJWKSは、トークンの署名を検証し、それが有効なトークンであることを保証するために必要な情報が含まれています。Auth0はOAuth2.0を実装しているため、トークンとそのプロパティの検証に使用される追加のメタデータを呼び出して取得できる「既知の」エンドポイントがあります。
  2. 次に、次の方法があります。 verify()
    1. このメソッドは、キーID(kidトークンヘッダーに存在するクレーム)を使用して、JWKSから使用されるキーを取得してトークンの署名を検証します。考えられるエラーのいずれかによってこのステップが失敗した場合、エラーメッセージが返されます。
    2. 次に、このメソッドは、これまでに収集された情報を使用してJWTをデコードしようとします。エラーの場合は、エラーメッセージを返します。成功すると、トークンペイロードが返されます。

前のセクションのプライベートエンドポイントでこのコードを使用する方法を見てみましょう。

Auth0アクセストークンを検証する

最後のパズルのピースは、ファイルに作成したクラスをインポートして、エンドポイントで使用することです。変更する必要があるものは次のとおりです。utils.pyGET /api/private

  1. importsセクションを更新して、のimport句を追加してVerifyTokenから、エンドポイントに移動します。また、エラーが発生した場合に詳細な応答を提供できるように、からResponseクラスとstatusオブジェクトをインポートする必要fastapiがあります。
  2. 次に、トークンをVerifyTokenクラスに渡し、メソッドの結果がエラーであるかどうかを確認して、エンドポイントを調整する必要があります。verify()

上記のすべての変更を加えたファイルは次のようになります。main.py

"""main.py
Python FastAPI Auth0 integration example
"""
 
from fastapi import Depends, FastAPI, Response, status  # 👈 new imports
from fastapi.security import HTTPBearer
 
from .utils import VerifyToken  # 👈 new import

# Scheme for the Authorization header
token_auth_scheme = HTTPBearer()
 
# Creates app instance
app = FastAPI()
 
 
@app.get("/api/public")
def public():
   """No access token required to access this route"""
 
   result = {
       "status": "success",
       "msg": ("Hello from a public endpoint! You don't need to be "
               "authenticated to see this.")
   }
   return result
 
 
@app.get("/api/private")
def private(response: Response, token: str = Depends(token_auth_scheme)):  # 👈 updated code
    """A valid access token is required to access this route"""
 
    result = VerifyToken(token.credentials).verify()  # 👈 updated code

    # 👇 new code
    if result.get("status"):
       response.status_code = status.HTTP_400_BAD_REQUEST
       return result
    # 👆 new code
 
    return result

この更新により、保護されたエンドポイントが適切に設定され、必要なアクセストークンのすべての検証手順が実行されます。🎉

--reload構成がロードされていることを確認する必要があるため、フラグを使用してサーバーを起動した場合でも、uvicornプロセスを終了してからサーバーを再起動することをお勧めします。これにより、.configファイルまたは環境変数の構成パラメーターを使用してAPIの適切な機能が保証されます。

FastAPIサーバー内の保護されたエンドポイントにリクエストを送信する前に、Auth0からのアクセストークンが必要です。TestAPIのタブにあるAuth0ダッシュボードからコピーして取得できます。

POSTAuth0のエンドポイントへのcurlリクエストを使用してアクセストークンを取得することもできます。このリクエストは、Auth0ダッシュボードのAPIのタブからコピーできます。カールリクエストは次のようになります。必要に応じて値を入力することを忘れないでください。oauth/tokenTest

curl -X 'POST' \
--url 'https://<YOUR DOMAIN HERE>/oauth/token' \
 --header 'content-type: application/x-www-form-urlencoded' \
 --data grant_type=client_credentials \
 --data 'client_id=<YOUR CLIENT ID HERE>' \
 --data client_secret=<YOUR CLIENT SECRET HERE> \
 --data audience=<YOUR AUDIENCE HERE>

コマンドラインに、次のようなベアラートークンを含む応答が表示されます。

{
    "access_token": "<YOUR_BEARER_TOKEN>",
    "expires_in": 86400,
    "token_type": "Bearer"
}

これで、このアクセストークンを使用してプライベートエンドポイントにアクセスできます。

curl -X 'GET' \
--url 'http://127.0.0.1:8000/api/private' \
--header  'Authorization: Bearer <YOUR_BEARER_TOKEN>'

リクエストが成功すると、サーバーはアクセストークンのペイロードを送り返します。

{
    "iss": "https://<YOUR_DOMAIN>/",
    "sub": "iojadoijawdioWDasdijasoid@clients",
    "aud": "http://<YOUR_AUDIENCE>",
    "iat": 1630341660,
    "exp": 1630428060,
    "azp": "ADKASDawdopjaodjwopdAWDdsd",
    "gty": "client-credentials"
}

検証が失敗した場合は、何が悪かったのかを詳細に確認する必要があることに注意してください。

これで、プライベートエンドポイントの保護とその保護のテストが完了しました。

要約

あなたはこのブログ投稿でかなり多くのことを学びました。まず、FastAPI2つのエンドポイント(1つはパブリック、もう1つはプライベート)を実装することで、の基本を学びました。これらのエンドポイントの両方にリクエストを送信するのがいかに簡単であるかを見てきました。検証クラスを作成し、PyJWTがAuth0アクセストークンの検証にどのように役立つかを確認し、JWKSとは何かを学びました。

Auth0ダッシュボードでAPIを作成するプロセスを実行しました。また、FastAPIが提供する依存性注入システムを活用して統合の実装を支援することにより、エンドポイントの1つを保護する方法も学びました。そして、あなたはこれらすべてを非常に迅速に行いました。

つまり、FastAPIエンドポイントを保護するためにAuth0を使用する方法だけでなく、を起動して実行するのがいかに簡単であるかを学びました。

このGitHubリポジトリには、今日作成したサンプルアプリケーションの完全なコードが含まれています。ご不明な点がございましたら、コミュニティフォーラムのスレッドでこのブログ投稿をお尋ねください。

リンク: https://auth0.com/blog/build-and-secure-fastapi-server-with-auth0/

#auth0 #security #fastapi