ホームページ  >  記事  >  バックエンド開発  >  依存関係インジェクションを使用した FastAPI 認証

依存関係インジェクションを使用した FastAPI 認証

Patricia Arquette
Patricia Arquetteオリジナル
2024-09-24 06:21:091020ブラウズ

FastAPI Auth with Dependency Injection

FastAPI は、Python で API を構築するための最新の Web フレームワークです。これは、OpenAPI 仕様のサポートが組み込まれており (つまり、バックエンド コードを記述し、そこからすべてを生成できます)、依存関係の注入 をサポートしているため、私の個人的なお気に入りの Web フレームワークの 1 つです。

この投稿では、FastAPI の depends がどのように機能するかを簡単に説明します。次に、なぜそれが認証と認可にうまく当てはまるのかを見ていきます。また、認証のもう 1 つの一般的なオプションであるミドルウェアとの対比も行います。最後に、FastAPI でのより高度な承認パターンをいくつか見ていきます。

依存関係の注入とは何ですか?

FastAPI のより強力な機能の 1 つは、依存関係の注入 のファーストクラスのサポートです。 こちらには長いガイドがありますが、その使用方法の簡単な例を見てみましょう。

ページ分割された API を構築しているとします。各 API 呼び出しには、page_number と page_size が含まれる場合があります。ここで、API を作成してこれらのパラメータを直接取り込むことができます。

@app.get("/things/")
async def fetch_things(page_number: int = 0, page_size: int = 100):
    return db.fetch_things(page_number, page_size)

しかし、おそらく、誰も page_number -1 や page_size 10,000,000 を要求しないように、いくつかの検証ロジックを追加したいと考えています。

@app.get("/things/")
async def fetch_things(page_number: int = 0, page_size: int = 100):
    if page_number < 0:
        raise HTTPException(status_code=400, detail="Invalid page number")
    elif page_size <= 0:
        raise HTTPException(status_code=400, detail="Invalid page size")
    elif page_size > 100:
        raise HTTPException(status_code=400, detail="Page size can be at most 100")
    return db.fetch_things(page_number, page_size)

これは問題ありませんが、すべて同じページング パラメータを必要とする 10 個の API または 100 個の API がある場合、少し面倒になります。ここで依存関係の注入が登場します。このロジックをすべて関数に移動し、その関数を API に注入できます。

async def paging_params_dep(page_number: int = 0, page_size: int = 100):
    if page_number < 0:
        raise HTTPException(status_code=400, detail="Invalid page number")
    elif page_size <= 0:
        raise HTTPException(status_code=400, detail="Invalid page size")
    elif page_size > 100:
        raise HTTPException(status_code=400, detail="Page size can be at most 100")
    return PagingParams(page_number, page_size)

@app.get("/things/")
async def fetch_things(paging_params: PagingParams = Depends(paging_params_dep)):
    return db.fetch_things(paging_params)

@app.get("/other_things/")
async def fetch_other_things(paging_params: PagingParams = Depends(paging_params_dep)):
    return db.fetch_other_things(paging_params)

これにはいくつかの素晴らしい利点があります:

  • PagingParams を受け取る各ルートは自動的に検証され、デフォルト値が設定されます。

  • 各ルートの最初の行を validate_paging_params(page_number, page_size) にするよりも冗長でなく、エラーが発生しやすくなります

  • これは FastAPI の OpenAPI サポートでも動作します。これらのパラメーターは OpenAPI 仕様に表示されます。

これは認証とどのような関係がありますか?

これは認証をモデル化する優れた方法でもあることがわかりました。次のような関数があると想像してください:

async def validate_token(token: str):
    try:
        # This could be JWT validation, looking up a session token in the DB, etc.
        return await get_user_for_token(token)
    except:
        return None

これを API ルートに接続するには、依存関係でラップするだけです。

async def require_valid_token_dep(req: Request):
    # This could also be a cookie, x-api-key header, etc.
    token = req.headers["Authorization"]
    user = await validate_token(token)
    if user == None:
        raise HTTPException(status_code=401, detail="Unauthorized")
    return user

そして、保護されているすべてのルートに次の依存関係を追加できます。

@app.get("/protected")
async def do_secret_things(user: User = Depends(require_valid_token_dep)):
    # do something with the user

ユーザーが有効なトークンを提供すると、このルートが実行され、ユーザーが設定されます。それ以外の場合は、401 が返されます。

: OpenAPI/Swagger には、認証トークンを指定するためのファーストクラスのサポートがありますが、専用のクラスの 1 つを使用する必要があります。 req.headers["Authorization"] の代わりに、HTTPAuthorizationCredentials を返す fastapi.security の HTTPBearer(auto_error=False) を使用できます。

ミドルウェアと認証依存

FastAPI は、ほとんどのフレームワークと同様、ミドルウェアの概念を持っています。ミドルウェアには、リクエストの前後に実行されるコードを含めることができます。リクエストがルートに到達する前にリクエストを変更でき、ユーザーに返される前にレスポンスを変更できます。

他の多くのフレームワークでは、ミドルウェアは認証チェックが行われる非常に一般的な場所です。ただし、これは多くの場合、ミドルウェアがユーザーをルートに「挿入」する役割も担っているためです。たとえば、Express での一般的なパターンは次のようなことを行うことです:

app.get("/protected", authMiddleware, (req, res) => {
    // req.user is set by the middleware
    // as there's no good way to pass in extra information into this route,
    // outside of the request
});

FastAPI にはインジェクションの概念が組み込まれているため、ミドルウェアを使用する必要がまったくない場合があります。認証トークンを定期的に (有効な状態に保つために) 「更新」し、応答を Cookie として設定する必要がある場合は、ミドルウェアの使用を検討します。

この場合、request.state を使用してミドルウェアからルートに情報を渡します (また、必要に応じて依存関係を使用して request.state を検証することもできます)。

それ以外の場合、ユーザーは request.state を経由せずにルートに直接挿入されるため、Depends を使い続けることになります。

認可 - マルチテナント、ロール、権限

これまでに学んだことをすべて適用すると、マルチテナンシー、ロール、または権限の追加は非常に簡単になります。各顧客に固有のサブドメインがあるとします。このサブドメインに依存関係を作成できます。

async def tenant_by_subdomain_dep(request: Request) -> Optional[str]:
    # first we get the subdomain from the host header
    host = request.headers.get("host", "")
    parts = host.split(".")
    if len(parts) <= 2:
        raise HTTPException(status_code=404, detail="Not found")
    subdomain = parts[0]

    # then we lookup the tenant by subdomain
    tenant = await lookup_tenant_for_subdomain(subdomain)
    if tenant == None:
        raise HTTPException(status_code=404, detail="Not found")
    return tenant

このアイデアを以前のアイデアと組み合わせて、新しい「マルチテナント」依存関係を作成できます。

async def get_user_and_tenant_for_token(
    user: User = Depends(require_valid_token_dep),
    tenant: Tenant = Depends(tenant_by_subdomain_dep),
) -> UserAndTenant:
    is_user_in_tenant = await check_user_is_in_tenant(tenant, user)
    if is_user_in_tenant:
        return UserAndTenant(user, tenant)
    raise HTTPException(status_code=403, detail="Forbidden")

次に、この依存関係をルートに挿入できます。

@app.get("/protected")
async def do_secret_things(user_and_tenant: UserAndTenant = Depends(get_user_and_tenant_for_token)):
    # do something with the user and tenant

これにより、いくつかの重要なことが行われます:

  • ユーザーが有効なトークンを持っているかどうかを確認しています

  • ユーザーが有効なサブドメインにリクエストを行っているかどうかを確認しています

  • ユーザーがそのサブドメインにアクセスできるかどうかを確認しています

If any of those invariants aren’t met - an error is returned and our route will never run. We can extend this to include other things like roles & permissions (RBAC) or making sure the user has a certain property set (active paid subscription vs no active subscription).

PropelAuth <3 FastAPI

At PropelAuth, we’re big fans of FastAPI. We have a FastAPI library that will enable you to set up authentication and authorization quickly - including SSO, Enterprise SSO / SAML, SCIM Provisioning, and more.

And it all works with dependencies like the ones you’ve seen above, e.g.:

@app.get("/")
async def root(current_user: User = Depends(auth.require_user)):
    return {"message": f"Hello {current_user.user_id}"}

You can find out more here.

Summary

  • FastAPI's dependency injection provides a powerful way to handle authentication and authorization in web applications.

  • The Depends feature allows for clean, reusable code for validating tokens, checking user permissions, and handling multi-tenancy.

  • Compared to middleware, using dependencies for auth offers more flexibility and direct integration with route functions.

  • Complex authorization scenarios like multi-tenancy and role-based access control can be efficiently implemented using nested dependencies.

  • PropelAuth offers a FastAPI library that simplifies the implementation of advanced authentication and authorization features.

以上が依存関係インジェクションを使用した FastAPI 認証の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。