Khi frontend gửi token qua header X-Token hoặc trình duyệt gửi session qua cookie, bạn không cần mò mẫm trong request.headers hay request.cookies. FastAPI có sẵn hai tham số khai báo Header() và Cookie(): khai báo một dòng là đọc được giá trị, thiếu thì nhận None hoặc lỗi 422 rõ ràng, đồng thời tự hiện trong tài liệu OpenAPI. Cách này dùng cho mọi endpoint cần đọc token, API key, ngôn ngữ, hoặc session id từ cookie.

Vấn đề

Nhiều người mới viết FastAPI hay đọc header thủ công qua đối tượng Request:

from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/whoami")
def whoami(request: Request):
token = request.headers["X-Token"] # KeyError nếu client quên gửi
session_id = request.cookies.get("session_id")
return {"x_token": token, "session_id": session_id}

Cách này có ba điểm yếu. Thứ nhất, request.headers["X-Token"] ném KeyError và trả lỗi 500 khi client quên gửi header — lỗi của client lại thành lỗi của server. Thứ hai, bạn phải tự kiểm tra None, tự validate, tự trả lỗi cho từng header. Thứ ba, Swagger UI không biết endpoint cần header hay cookie nào, nên người gọi API phải đoán mò.

Giải pháp

FastAPI coi header và cookie như mọi tham số khác: khai báo kiểu dữ liệu và giá trị mặc định là xong.

from typing import Annotated
from fastapi import Cookie, FastAPI, Header
app = FastAPI()
@app.get("/whoami")
def whoami(
x_token: Annotated[str | None, Header()] = None,
session_id: Annotated[str | None, Cookie()] = None,
):
return {"x_token": x_token, "session_id": session_id}

Ba quy tắc cần nhớ. Thứ nhất, tên biến dùng gạch dưới, FastAPI tự đổi thành gạch ngang: x_token đọc header X-Token, và header không phân biệt hoa thường. Thứ hai, đặt mặc định = None thì tham số là tùy chọn: thiếu header, endpoint vẫn chạy và nhận None. Bỏ mặc định thì header thành bắt buộc, thiếu là FastAPI trả lỗi 422 trước khi code của bạn chạy.

@app.get("/secure")
def secure(x_api_key: Annotated[str, Header()]):
return {"ok": True}

Thứ ba, nếu header gốc chứa gạch dưới thật (ví dụ X_Custom), truyền convert_underscores=False, nếu không FastAPI sẽ tìm header X-Custom và nhận None.

@app.get("/legacy")
def legacy(
x_custom: Annotated[str | None, Header(convert_underscores=False)] = None,
):
return {"x_custom": x_custom}

Áp dụng vào dự án của bạn

Dùng mẫu này cho mọi endpoint cần xác thực nhẹ: đọc X-Api-Key để kiểm tra API key, đọc Accept-Language để trả nội dung đúng ngôn ngữ, đọc session_id từ cookie để biết người dùng đã đăng nhập chưa. Nhưng đừng tin cookie mù: hãy ký cookie hoặc kiểm tra session trong database hoặc Redis trước khi cho qua. Khi logic kiểm tra lặp lại ở nhiều endpoint, bọc nó vào một dependency dùng chung thay vì khai báo lại từng nơi.

Bài viết liên quan

Câu hỏi thường gặp

Header() khác gì đọc request.headers trực tiếp?

Khai báo Header() rõ ràng hơn: FastAPI tự validate kiểu dữ liệu, trả lỗi 422 chuẩn khi thiếu header bắt buộc, và tự sinh tài liệu OpenAPI cho Swagger UI. Đọc trực tiếp thì bạn phải tự làm cả ba việc đó.

Khi nào dùng Cookie() thay vì Header()?

Cookie do trình duyệt tự gửi kèm mỗi request cùng domain, hợp cho session đăng nhập web. Header hợp cho API key hoặc token do client chủ động gắn, đặc biệt khi gọi từ app mobile hoặc từ một server khác, nơi không có cơ chế cookie của trình duyệt.

Sao client gửi X-Token mà server vẫn nhận None?

Kiểm tra tên header trước: FastAPI đổi gạch dưới thành gạch ngang, nên biến x_token chỉ đọc header X-Token, không đọc X_Token. Nếu hệ thống cũ bắt buộc dùng gạch dưới, thêm convert_underscores=False. Header không phân biệt hoa thường nên lỗi hoa thường không phải nguyên nhân.

Cookie có đọc được từ domain khác không?

Không. Trình duyệt chỉ gửi cookie của đúng domain đã đặt nó. Muốn chia sẻ session qua domain khác, cấu hình domain và path khi đặt cookie, đồng thời bật CORS với allow_credentials=True và liệt kê rõ origin được phép — không dùng allow_origins=["*"] khi bật credentials.

💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.

Nguồn tham khảo