Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/how-to/new-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,23 @@ def create_app(settings: AppSettings | None = None) -> FastAPI:
limit=settings.throttle_limit,
window=settings.throttle_window,
)
# Auth middleware — registered before CORS so it sits inside the CORS layer.
if settings.bearer_token_enabled:
app.add_middleware(BearerTokenMiddleware, verifier=LocalTokenVerifier(settings.bearer_tokens))
if settings.api_key_enabled:
app.add_middleware(ApiKeyAuthMiddleware, verifier=LocalTokenVerifier(settings.api_keys))
# CORS must be the outermost layer — register it last.
# OPTIONS preflight requests must reach CORSMiddleware before any auth check.
# If CORSMiddleware is registered before auth middleware, the auth layer becomes
# outermost and returns 401 on preflight, breaking CORS for all browsers.
if settings.cors_enabled:
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=settings.cors_allow_credentials,
allow_methods=settings.cors_allow_methods,
allow_headers=settings.cors_allow_headers,
)

# Convert Pydantic BaseModel validation errors to RFC 9457 Problem Details
app.add_exception_handler(RequestValidationError, request_validation_error_handler) # type: ignore[arg-type]
Expand All @@ -117,6 +134,8 @@ app = create_app()

> **Middleware ordering note:** Starlette's `add_middleware` applies middleware in reverse registration order — the last registered becomes the outermost layer. Register `ErrorHandlerMiddleware` first so it wraps everything and catches all unhandled exceptions.

> **CORS + Auth rule**: Always register `CORSMiddleware` *after* any auth middleware. In Starlette's reverse order, "last registered = outermost" means CORS wraps auth, so browser preflight (`OPTIONS`) requests are handled before authentication.

## 6. Run the development server

```bash
Expand Down
46 changes: 46 additions & 0 deletions docs/ja/reference/framework-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,52 @@ cfg_test = AppSettings(throttle_enabled=False) # テスト用オーバー
| `RequestSizeLimitMiddleware` | `nene2.middleware.request_size_limit` | ペイロードサイズ制限 |
| `ThrottleMiddleware` | `nene2.middleware.throttle` | 固定ウィンドウ レートリミット |

#### `add_middleware` 引数

| ミドルウェア | キーワード引数 | デフォルト |
|---|---|---|
| `ErrorHandlerMiddleware` | `debug: bool`, `domain_handlers: list[DomainExceptionHandlerProtocol] \| None` | `False`, `None` |
| `SecurityHeadersMiddleware` | *(なし)* | — |
| `RequestIdMiddleware` | *(なし)* | — |
| `RequestLoggingMiddleware` | *(なし)* | — |
| `RequestSizeLimitMiddleware` | `max_bytes: int` | `1_048_576` (1 MiB) |
| `ThrottleMiddleware` | `limit: int`, `window: int` | `60`, `60` |

`ThrottleMiddleware` には `enabled` フラグがありません。`if settings.throttle_enabled:` でラップして制御します。

#### 完全な登録順(任意ミドルウェア含む)

```python
# 登録順: 最内側から最外側へ。Starlette は逆順に実行します(最後に登録したものが最外側)。
app.add_middleware(ErrorHandlerMiddleware, debug=settings.app_debug, domain_handlers=[...])
app.add_middleware(SecurityHeadersMiddleware)
app.add_middleware(RequestIdMiddleware)
app.add_middleware(RequestLoggingMiddleware)
app.add_middleware(RequestSizeLimitMiddleware, max_bytes=settings.max_body_size)
if settings.throttle_enabled:
app.add_middleware(ThrottleMiddleware, limit=settings.throttle_limit, window=settings.throttle_window)
# Auth ミドルウェア — CORS より前に登録して CORS レイヤーの内側に配置する
if settings.bearer_token_enabled:
app.add_middleware(BearerTokenMiddleware, verifier=LocalTokenVerifier(settings.bearer_tokens))
if settings.api_key_enabled:
app.add_middleware(ApiKeyAuthMiddleware, verifier=LocalTokenVerifier(settings.api_keys))
# CORS は最外側に配置 — 必ず最後に登録する。
# OPTIONS preflight リクエストは Auth チェックの前に CORSMiddleware に到達しなければならない。
# CORSMiddleware を Auth より前に登録すると、Auth が最外側になり preflight が 401 になる。
if settings.cors_enabled:
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=settings.cors_allow_credentials,
allow_methods=settings.cors_allow_methods,
allow_headers=settings.cors_allow_headers,
)
```

> **CORS + Auth ルール**: `CORSMiddleware` は Auth ミドルウェアの*後に*登録してください。
> Starlette の逆順ルールにより「最後に登録 = 最外側」となり、CORS が Auth をラップします。
> これによりブラウザの preflight(`OPTIONS`)リクエストが認証前に処理されます。

---

## nene2.auth
Expand Down
28 changes: 26 additions & 2 deletions docs/reference/framework-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,19 +149,43 @@ Starlette applies middleware in **reverse registration order** — the last regi
| `RequestSizeLimitMiddleware` | `max_bytes: int` | `1_048_576` (1 MiB) |
| `ThrottleMiddleware` | `limit: int`, `window: int` | `60`, `60` |

`ThrottleMiddleware` has no `enabled` flag — wrap with `if settings.throttle_enabled:` to disable it:
`ThrottleMiddleware` has no `enabled` flag — wrap with `if settings.throttle_enabled:` to disable it.

#### Full registration order with optional middleware

```python
# Correct registration order (innermost → outermost)
# Registration order: innermost first, outermost last.
# Starlette executes in reverse — the last registered wraps all others.
app.add_middleware(ErrorHandlerMiddleware, debug=settings.app_debug, domain_handlers=[...])
app.add_middleware(SecurityHeadersMiddleware)
app.add_middleware(RequestIdMiddleware)
app.add_middleware(RequestLoggingMiddleware)
app.add_middleware(RequestSizeLimitMiddleware, max_bytes=settings.max_body_size)
if settings.throttle_enabled:
app.add_middleware(ThrottleMiddleware, limit=settings.throttle_limit, window=settings.throttle_window)
# Auth middleware — registered before CORS so it sits inside the CORS layer
if settings.bearer_token_enabled:
app.add_middleware(BearerTokenMiddleware, verifier=LocalTokenVerifier(settings.bearer_tokens))
if settings.api_key_enabled:
app.add_middleware(ApiKeyAuthMiddleware, verifier=LocalTokenVerifier(settings.api_keys))
# CORS must be the outermost layer — register it last.
# OPTIONS preflight requests must reach CORSMiddleware before any auth check.
# If CORSMiddleware is registered before auth middleware, the auth layer becomes
# outermost and returns 401 on preflight, breaking CORS for all browsers.
if settings.cors_enabled:
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=settings.cors_allow_credentials,
allow_methods=settings.cors_allow_methods,
allow_headers=settings.cors_allow_headers,
)
```

> **CORS + Auth rule**: Always register `CORSMiddleware` *after* any auth middleware.
> In Starlette's reverse order, "last registered = outermost" means CORS wraps auth,
> so browser preflight (`OPTIONS`) requests are handled before authentication.

---

## nene2.auth
Expand Down
Loading