---
# System prepended metadata

title: authorization
tags: [Python / FastAPI, PyPi, backend, Python, fastapi]

---

authorization
===
###### tags: `Python / FastAPI`
###### tags: `Python`, `PyPi`, `backend`, `fastapi`

<br>

[TOC]

<br>


## 討論

### 如何用 curl 進行驗證 basic auth
- ### Question
    ```python=
    security = HTTPBasic()


    def get_openai_permission(
        credentials: HTTPBasicCredentials = Depends(security),
    ) -> str:
        """
        Get openai permission.

        :param credentials: credentials.
        :returns: credentials username
        :raises HTTPException: if call to something fails...
        """
        correct_username = secrets.compare_digest(
            credentials.username,
            settings.openapi_dev_account,
        )
        correct_password = secrets.compare_digest(
            credentials.password,
            settings.openapi_dev_password,
        )
    ```
    我要如何用 curl 打 request，取出 `/api/docs` ?
- ### gpt-4
    若要使用 `curl` 請求 HTTP 基礎認證保護的 FastAPI `/api/docs` 端點，你需要將使用者名稱和密碼以 Base64 編碼的方式傳遞給 `Authorization` 頭部。以下是一個 `curl` 請求的示例：
    ```bash
    curl -X 'GET' \
      'http://localhost:8000/api/docs' \
      -H 'accept: text/html' \
      -H 'Authorization: Basic <base64_encoded_credentials>'
    ```
    在上面的命令中，你需要將 `<base64_encoded_credentials>` 替換為你的使用者名稱和密碼的 Base64 編碼。編碼的格式是 `username:password`。你可以在大多數的 Unix 系統（包括 macOS）和 Windows 的命令行中使用 `echo` 和 `base64` 命令來生成這個編碼：

    ```bash
    echo -n "your_username:your_password" | base64
    ```
    - `-n`: do not output the trailing newline

    將 `your_username` 和 `your_password` 替換為你的憑證。命令的輸出將是 Base64 編碼的字符串，你需要將這個字符串插入到 curl 命令的 Authorization 頭部中。

    如果你使用的是 Windows，則可以在 PowerShell 中使用以下命令來生成 Base64 編碼的憑證：

    ```powershell
    [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("your_username:your_password"))
    ```
    在 `curl` 命令中使用生成的 Base64 編碼後，就能對 `/api/docs` 端點發出請求並獲得回應。如果認證正確且你有訪問該端點的權限，你將能夠取得 API 文件。


<br>

<hr>

<br>

### FastAPI 中使用自訂 Middleware 驗證 vs `starlette.authentication` 差異比較
- ### Question
    自訂 Middleware 驗證（透過 `openapi_schema` 加入 `BearerAuth` 驗證），
    跟使用 starlette.authentication 機制差別在哪裡？
- ### gpt-4o (2025/03/31)

    > FastAPI 中使用自訂 Middleware 驗證 vs `starlette.authentication` 差異比較

    這份紀錄說明了兩種在 FastAPI 中實作 API Token 驗證的方式：

    1. 使用自訂 middleware（你目前的實作）
    2. 使用 Starlette 提供的 `AuthenticationMiddleware` + `AuthenticationBackend`

    ---

    ### ✅ 簡單比較表

    | 項目 | 自訂 middleware 驗證方式 | Starlette `AuthenticationMiddleware` |
    |------|---------------------------|--------------------------------------|
    | 驗證方式 | 自訂 middleware 檢查 `Authorization` header | 實作 `AuthenticationBackend` 類別 |
    | 擴展性 | 需自行控制驗證流程與例外情境 | 提供標準介面與流程 |
    | 使用者資訊 | 無 `request.user` 可用 | 提供 `request.user`, `request.auth` |
    | 使用範圍 | 全域 middleware 判斷所有路由 | 可搭配權限控制、使用者群組等設計 |
    | 路由控制 | 需手動跳過特定路由驗證（如 `/docs`） | 可在 `backend` 或 route 裡統一處理 |
    | 錯誤回應 | 自行回傳 401/403 錯誤訊息 | 可統一處理或自訂錯誤格式 |

    ---

    ### 🧠 機制說明

    #### 1. 你目前的自訂 Middleware 驗證方式

    **優點**
    - 快速開發，簡單直覺。
    - 不需額外套件或介面實作。

    **缺點**
    - 所有驗證邏輯都必須手動撰寫與維護。
    - 無法使用 `request.user` 或權限控制機制。
    - 不利擴充與測試，例如加入 OAuth、JWT、RBAC 等。

    ---

    #### 2. 使用 Starlette 的 AuthenticationMiddleware

    Starlette 提供更結構化的驗證流程：

    ##### ✅ 建立自訂驗證後端 `AuthenticationBackend`

    使用者需自行實作：

    ```python=
    from starlette.authentication import (
        AuthenticationBackend, AuthCredentials, SimpleUser
    )

    class BearerTokenBackend(AuthenticationBackend):
        async def authenticate(self, conn):
            auth = conn.headers.get("Authorization")
            if not auth or not auth.lower().startswith("bearer "):
                return

            token = auth[7:].strip()
            if token != "expected_token":
                return

            return AuthCredentials(["authenticated"]), SimpleUser("client")
    ```

    ---

    ##### ✅ 將驗證後端掛載至 Middleware

    ```python=
    from starlette.middleware.authentication import AuthenticationMiddleware

    app.add_middleware(
        AuthenticationMiddleware,
        backend=BearerTokenBackend()
    )
    ```

    ---

    ##### ✅ 在路由中使用驗證結果

    ```python=
    @app.get("/secure-data")
    async def secure_data(request: Request):
        if not request.user.is_authenticated:
            raise HTTPException(status_code=403, detail="Unauthorized")

        return {"message": f"Hello {request.user.display_name}"}
    ```

    ---

    ### 🔁 適用情境

    **使用 Starlette Auth 的建議情境：**

    - 需要多種登入機制（Token, Cookie, OAuth 等）。
    - 想在路由中存取 `user`, `auth` 等資訊。
    - 需要支援 RBAC、ACL、群組/權限控管。
    - 希望將驗證模組化並進行單元測試。

    ---

    ### ✅ 混合使用建議

    如果目前只需要簡單的 Bearer token 驗證，自訂 middleware 是夠用的。

    **但若預期功能成長，建議轉向使用 `AuthenticationMiddleware`，因為：**

    - 更好維護與測試
    - 統一驗證邏輯
    - 支援更多進階安全機制

    ---

    ### 🚀 想進一步優化？

    如果你希望我幫你 refactor 成基於 Starlette 的驗證方式，
    並保留現有的 token 驗證邏輯（例如從 K8s 拿 token 等），
    我可以幫你提供完整的範本 + 測試方式。


<br>

<hr>

<br>

### 使用 Starlette AuthenticationMiddleware 重構 Bearer Token 驗證
- ### Question
    (承上)進一步優化
- ### gpt-4o (2025/03/31)

    > ⭐ 使用 Starlette AuthenticationMiddleware 重構 Bearer Token 驗證

    本文說明如何將現有 FastAPI + middleware 驗證邏輯，改寫成使用 Starlette 的 `AuthenticationMiddleware` + 自訂 `AuthenticationBackend`。此設計可支援 `request.user`、更彈性的 RBAC 與可測試架構。

    ---

    ### 📦 專案結構建議

    ```
    app/
    ├── main.py
    ├── auth/
    │   ├── backend.py      👈 Bearer token 驗證邏輯
    │   └── models.py       👈 User/Auth 資料結構（可選）
    ├── utils/
    │   └── kubernetes_helper2.py
    └── config/
        └── settings.py
    ```

    ---

    ### 🔐 `backend.py` — 自訂 BearerTokenAuthBackend

    ```python=
    # app/auth/backend.py

    from typing import Optional, Tuple

    from starlette.authentication import (
        AuthCredentials, AuthenticationBackend,
        BaseUser, UnauthenticatedUser
    )
    from starlette.requests import HTTPConnection

    from app.utils.kubernetes_helper2 import k8s_helper2


    class SimpleUser(BaseUser):
        """Represents an authenticated user with a simple ID."""

        def __init__(self, username: str):
            self.username = username

        @property
        def is_authenticated(self) -> bool:
            return True

        @property
        def display_name(self) -> str:
            return self.username


    class BearerTokenAuthBackend(AuthenticationBackend):
        """
        Authentication backend that validates Bearer tokens
        in the Authorization header.
        """

        async def authenticate(
            self,
            conn: HTTPConnection
        ) -> Optional[Tuple[AuthCredentials, BaseUser]]:
            auth_header = conn.headers.get("Authorization")

            if not auth_header or not auth_header.lower().startswith("bearer "):
                return None  # Unauthenticated

            token = auth_header[7:].strip()

            # Check against authorized tokens
            valid_tokens = k8s_helper2.get_client_api_tokens()
            if token not in valid_tokens:
                return None  # Invalid token

            # Assign roles/scopes as needed
            return AuthCredentials(["authenticated"]), SimpleUser("client_user")
    ```

    ---

    ### ⚙️ `main.py` — FastAPI 初始化與 middleware 設定

    ```python=
    # app/main.py

    from fastapi import FastAPI, Request, Depends, HTTPException
    from fastapi.responses import JSONResponse
    from starlette.middleware.authentication import AuthenticationMiddleware
    from starlette.authentication import requires

    from app.auth.backend import BearerTokenAuthBackend

    app = FastAPI()

    # Add authentication middleware
    app.add_middleware(
        AuthenticationMiddleware,
        backend=BearerTokenAuthBackend()
    )

    @app.get("/api/public")
    async def public_endpoint():
        return {"message": "No auth needed"}

    @app.get("/api/secure")
    @requires("authenticated")
    async def secure_endpoint(request: Request):
        return {
            "message": f"Hello, {request.user.display_name}",
            "user": str(request.user)
        }
    ```

    ---

    ### ✅ 驗證行為說明

    | Endpoint         | 驗證行為                     | 回應                          |
    |------------------|------------------------------|-------------------------------|
    | `/api/public`    | 不需驗證                     | 回傳成功                      |
    | `/api/secure`    | 需帶上 Bearer token          | 無效或缺少 → 403 Forbidden     |
    | `/api/secure`    | 有效 token → 顯示使用者資訊 | OK                           |

    ---

    ### ✨ 優勢

    - ✅ `request.user` 可用於 handler 中
    - ✅ 使用 `@requires("authenticated")` 做權限控管
    - ✅ 自訂 `SimpleUser` 可擴充 scopes, roles, group 等概念
    - ✅ 驗證邏輯可測試、模組化、易維護

    ---

    ### 🧪 測試範例

    ```
    curl -X GET http://localhost:8000/api/secure \
      -H "Authorization: Bearer <your_token>"
    ```

    ---

    ### 🧭 後續建議擴充方向

    1. ✅ 加入 **RBAC 設計**（支援角色與權限判斷）
    2. ✅ 自動設定 **OpenAPI securitySchemes**
    3. ✅ 根據 user 限制可存取的資源範圍（user-level scoping）

    ---

    如需上述功能範本，歡迎提出，我可以繼續協助補上實作 😎

<br>

<hr>

<br>

### FastAPI Authentication + RBAC Integration Guide
- ### Question
    (承上)一併補上
- ### gpt-4o (2025/03/31)
    > 🔐 FastAPI Authentication + RBAC Integration Guide

    這份文件說明如何將 FastAPI 與 Starlette AuthenticationMiddleware 整合，並加入：

    | 功能 | 說明 |
    |-----|-----|
    | ✅ Bearer Token 驗證 | 使用者身份驗證 |
    | ✅ RBAC 角色權限控制 | 使用者身份授權，根據 `admin`，`user`，`guest` 等給予不同的存取權限 |
    | ✅ 自訂 OpenAPI security schema | |
    | ✅ 使用者綁定資源查詢功能 | 根據 `request.user` 給予限制或授權 |

    ---

    ### 📦 專案結構建議

    ```
    app/
    ├── auth/
    │   ├── backend.py      👈 Bearer token 驗證與 RBAC
    │   ├── models.py       👈 User model with roles
    │   └── dependencies.py 👈 權限驗證器 (Depends 使用)
    ├── main.py
    ├── routes/
    │   └── client.py       👈 客戶端相關 API
    ├── utils/
    │   └── kubernetes_helper2.py
    ├── config/
    │   └── settings.py
    └── openapi/
        └── custom_openapi.py 👈 自訂 OpenAPI security
    ```

    ---

    ### 🔐 `models.py` — 使用者模型（支援角色）

    ```python=
    # app/auth/models.py

    from starlette.authentication import BaseUser


    class RoleUser(BaseUser):
        def __init__(self, username: str, roles: list[str]):
            self.username = username
            self.roles = roles

        @property
        def is_authenticated(self) -> bool:
            return True

        @property
        def display_name(self) -> str:
            return self.username

        def has_role(self, role: str) -> bool:
            return role in self.roles
    ```

    ---

    ### 🧠 `backend.py` — 驗證邏輯 + RBAC 判斷

    ```python=
    # app/auth/backend.py

    from typing import Optional, Tuple
    from starlette.authentication import (
        AuthCredentials, AuthenticationBackend
    )
    from starlette.requests import HTTPConnection

    from app.utils.kubernetes_helper2 import k8s_helper2
    from app.auth.models import RoleUser


    class BearerTokenAuthBackend(AuthenticationBackend):
        async def authenticate(
            self, conn: HTTPConnection
        ) -> Optional[Tuple[AuthCredentials, RoleUser]]:
            auth_header = conn.headers.get("Authorization")
            if not auth_header or not auth_header.lower().startswith("bearer "):
                return None

            token = auth_header[7:].strip()
            valid_tokens = k8s_helper2.get_client_api_tokens()
            if token not in valid_tokens:
                return None

            # 模擬從 token 判斷角色：abc123-admin → admin
            role = "admin" if token.endswith("-admin") else "user"

            user = RoleUser(username="client_user", roles=[role])
            return AuthCredentials(["authenticated"]), user
    ```

    ---

    ### 🔒 `dependencies.py` — 權限限制函式 (for Depends)

    ```python=
    # app/auth/dependencies.py

    from fastapi import Depends, HTTPException, Request, status


    def require_role(role: str):
        async def _role_dependency(request: Request):
            user = request.user
            if not user.is_authenticated or not user.has_role(role):
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail=f"Requires '{role}' role"
                )
            return user
        return _role_dependency
    ```

    ---

    ### 📄 `custom_openapi.py` — 加入 Bearer Token 安全標記

    ```python=
    # app/openapi/custom_openapi.py

    from fastapi.openapi.utils import get_openapi
    from fastapi import FastAPI


    def custom_openapi(app: FastAPI):
        if app.openapi_schema:
            return app.openapi_schema

        openapi_schema = get_openapi(
            title=app.title,
            version=app.version,
            description=app.description,
            routes=app.routes,
        )

        openapi_schema["components"]["securitySchemes"] = {
            "BearerAuth": {
                "type": "http",
                "scheme": "bearer"
            }
        }

        for path in openapi_schema["paths"].values():
            for method in path.values():
                method["security"] = [{"BearerAuth": []}]

        app.openapi_schema = openapi_schema
        return app.openapi_schema
    ```

    ---

    ### 🚀 `main.py` — 整合所有設定

    ```python=
    # app/main.py

    from fastapi import FastAPI
    from starlette.middleware.authentication import AuthenticationMiddleware

    from app.auth.backend import BearerTokenAuthBackend
    from app.openapi.custom_openapi import custom_openapi
    from app.routes import client

    app = FastAPI()

    app.add_middleware(
        AuthenticationMiddleware,
        backend=BearerTokenAuthBackend()
    )

    app.openapi = lambda: custom_openapi(app)

    app.include_router(client.router)
    ```

    ---

    ### 👥 `client.py` — 加入使用者驗證與角色控制

    ```python=
    # app/routes/client.py

    from fastapi import APIRouter, Request, Depends
    from app.auth.dependencies import require_role

    router = APIRouter(prefix="/api/client")


    @router.get("/me")
    async def get_self(request: Request):
        return {"user": request.user.display_name}


    @router.get("/admin-only")
    async def admin_only(
        request: Request,
        user=Depends(require_role("admin"))
    ):
        return {"message": f"Hello admin {user.display_name}"}
    ```

    ---

    ### 🧪 測試 API

    #### ✅ 一般 user 可用
    ```
    curl -H "Authorization: Bearer abc123-user" \
         http://localhost:8000/api/client/me
    ```

    #### ❌ user 權限存取 admin-only → 403
    ```
    curl -H "Authorization: Bearer abc123-user" \
         http://localhost:8000/api/client/admin-only
    ```

    #### ✅ admin 權限可以存取
    ```
    curl -H "Authorization: Bearer abc123-admin" \
         http://localhost:8000/api/client/admin-only
    ```

    ---

    ### 🔧 可擴充方向

    - ➕ 支援多種角色與細緻權限，如：`["admin", "editor", "viewer"]`
    - 🔑 加入 OAuth2 / JWT 驗證
    - 🗂️ 使用 DB 儲存使用者與角色
    - ✅ 使用 `request.user` 控制每位用戶可查詢的資料範圍

    ---

    如需擴充 JWT、OAuth、角色綁定資料庫等，歡迎加上，我可以協助進一步改寫。
    備註關鍵字：
    - `from fastapi.security import OAuth2PasswordBearer`

<br>

{%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}