Kết nối gần như hoàn thiện. Agent đã soạn xong cấu hình, nêu tên dịch vụ và dừng lại ở một trường - API key. Dịch vụ cấp cho bạn một chuỗi dài, và nhìn vào chuỗi đó thì không biết nên đặt nó ở đâu. Dán vào cuộc trò chuyện? Vào một tệp cạnh mã nguồn? Thẳng vào cấu hình cho chắc chắn chạy được?
Hãy tìm hiểu điều này thông qua một mô hình duy nhất. Chúng ta sẽ xem giá trị được đặt ở đâu, tiến trình nào nhận được nó, và cách xác minh mọi thứ đã được cấu hình mà không để lộ chính bí mật đó. Mô hình giống nhau cho việc chạy cục bộ, kết nối MCP và ứng dụng đã xuất bản - chỉ khác ở chỗ chúng ta đang cấu hình môi trường của ai.
Khóa truy cập có tác dụng gì
Khóa liên kết yêu cầu của bạn với tài khoản hoặc dự án, cùng với các quyền đã được cấp. Thông qua nó, dịch vụ biết ai đang gửi yêu cầu, người đó được phép làm gì, và đã gửi bao nhiêu yêu cầu. Ý nghĩa chính xác của khóa do từng dịch vụ quy định. Ở dịch vụ này, đó là quyền truy cập toàn bộ tài khoản, ở dịch vụ khác chỉ là một dự án và chỉ được đọc.

Trong các hướng dẫn, cạnh từ "khóa" hay xuất hiện từ token. Trong văn bản thông thường, hai từ này hay được dùng như nhau, nhưng chi tiết cơ chế thì khác. Đó là cách nhận, thời hạn hiệu lực và phạm vi quyền. Tốt nhất là không coi chúng là từ đồng nghĩa hoàn toàn - dịch vụ cấp cho bạn thứ gì thì xem trong tài liệu của nó.
Tại sao dịch vụ cần phân biệt bạn với người khác? Một ví dụ đời thường giải thích điều này rõ ràng. Bạn tạo tài khoản, chọn gói dịch vụ với hạn mức yêu cầu hàng tháng và nhận khóa. Sau đó, dịch vụ dùng khóa này để đếm yêu cầu của bạn, đối chiếu với hạn mức, và nếu gói là trả phí thì lập hóa đơn. Nếu khóa rơi vào tay người lạ, người đó sẽ dùng hạn mức của bạn, còn hóa đơn thì bạn trả.
Khóa không tự ra quyết định. Nó chỉ cho dịch vụ biết cần xem thiết lập của chủ sở hữu nào, còn chính dịch vụ mới quyết định yêu cầu cụ thể có được phép hay không và có cần trừ vào hạn mức hay không.
Cũng có những giá trị mà dịch vụ chủ ý đánh dấu là công khai - chúng được thiết kế để đặt trong mã trang. Không thể phân biệt giá trị công khai và giá trị bí mật qua tên biến. Mục đích được ghi trong tài liệu, cần xem ở đó. Trong các ví dụ dưới đây, chỗ nào cũng là giá trị giả dễ nhận ra như fc-EXAMPLE-0000000000, khóa thật không trông như vậy.
Nơi ứng dụng có thể lưu trữ bí mật
Hãy tưởng tượng một tình huống như thế này. Bạn thêm khóa trực tiếp vào mã trang, chạy dự án và nhận phản hồi từ API. Mọi thứ đều hoạt động. Sau khi xuất bản, ứng dụng cũng mở được, yêu cầu vẫn chạy - có vẻ như việc đã xong.
Nhưng cùng với mã, trình duyệt của mỗi khách truy cập cũng nhận được khóa. Chỉ cần mở công cụ dành cho nhà phát triển, tìm nó trong JavaScript hoặc trong yêu cầu rồi sao chép. Sau đó, người này có thể gửi yêu cầu của riêng họ dưới danh nghĩa của bạn. Các yêu cầu đó sẽ tiêu hạn mức của bạn cho đến khi dịch vụ bắt đầu từ chối chính các yêu cầu của bạn.
Vấn đề không nằm ở một công cụ build cụ thể nào. Mọi thứ trang gửi xuống trình duyệt đều nằm trong tay chủ của trình duyệt đó: HTML, CSS, JavaScript, địa chỉ và header của yêu cầu. Ngay cả khi lúc build khóa được lấy từ tệp cục bộ .env, sau khi được chèn vào JavaScript, nó vẫn đến tay khách truy cập cùng với phần còn lại của mã.
Backend là mã chạy trên máy chủ. Khách truy cập nhận được phản hồi của nó, nhưng không nhận được mã nguồn hay biến môi trường của tiến trình trên máy chủ. Vì vậy, backend có thể giữ khóa ở phía mình và gọi dịch vụ bên ngoài thay mặt ứng dụng.
Đường đi của yêu cầu thay đổi. Trình duyệt gửi một yêu cầu bình thường đến backend của bạn, không kèm khóa bí mật. Backend đọc khóa từ môi trường của mình, gắn nó vào một yêu cầu riêng gửi đến API và chỉ trả kết quả về cho trình duyệt.
Bây giờ khách truy cập thấy địa chỉ backend và dữ liệu phản hồi, nhưng không thấy khóa của dịch vụ bên ngoài. Bí mật ở lại với tiến trình sử dụng nó.
Bí mật có thể thuộc về một trong hai nơi:
- tiến trình cục bộ - Claude Code với tích hợp đã kết nối, một script hoặc dev server. Nó chạy trên máy tính của bạn và nhận khóa ngay tại đó;
- backend của ứng dụng đã xuất bản - mã trên máy chủ tự gọi API bên ngoài. Môi trường của nó được cấu hình trong bảng điều khiển của hosting.
Đây là hai chỗ cấu hình riêng biệt. Khóa trong môi trường máy tính của bạn sẽ không tự xuất hiện trên hosting, ngay cả khi dịch vụ và tên biến trùng nhau.
Chuyển khóa sang backend sẽ giấu nó khỏi trình duyệt, nhưng một backend công khai vẫn cần được bảo vệ khỏi yêu cầu của người lạ.
Trước khi Claude Code đề nghị bạn đặt giá trị, hãy yêu cầu nó nói rõ hai điều: tiến trình nào sẽ dùng khóa và giá trị sẽ đến được tiến trình đó bằng cách nào. Nếu chưa có câu trả lời, thì chưa có gì để cấu hình cả.
Biến môi trường hoạt động như thế nào
Biến môi trường là giá trị được đặt tên mà chương trình nhận được khi khởi động. Nó có tên, chẳng hạn FIRECRAWL_API_KEY, và giá trị, chính là chuỗi dài kia. Trong cấu hình và mã, người ta viết tên; giá trị được giữ riêng và không nằm cạnh mã.
Chương trình lấy giá trị từ đâu? Chương trình chạy từ terminal sẽ kế thừa môi trường của terminal đó. Đặt biến trong một cửa sổ, chạy Claude Code từ chính cửa sổ đó - Claude Code sẽ thấy biến. Mở cửa sổ thứ hai và đặt biến ở đó - tiến trình đang chạy không bị ảnh hưởng, vì nó đã nhận môi trường lúc khởi động.
Đây chính là nguyên nhân phổ biến nhất của lời than "đã đặt khóa rồi mà không chạy". Giá trị được đặt nhầm cửa sổ hoặc đặt sau khi chương trình đã khởi động. Cách chữa là khởi động lại tiến trình từ terminal có biến đó.
Phiên bản để học, với giá trị rõ ràng là giả:
# bash: biến cho terminal hiện tại
export FIRECRAWL_API_KEY=fc-EXAMPLE-0000000000# PowerShell: tương tự
$env:FIRECRAWL_API_KEY = "fc-EXAMPLE-0000000000"Với giá trị giả trong ví dụ để học thì viết như vậy không sao. Còn khóa thật thì không nên nhập theo cách này. Cả chuỗi sẽ lọt vào lịch sử lệnh và vẫn nằm đó sau khi bạn đóng terminal. Với giá trị thật, có cách nhập không hiển thị - bạn dán chuỗi vào, màn hình không hiện gì, và lịch sử chỉ lưu bản thân câu lệnh:
# bash: giá trị được nhập ẩn
read -s -r FIRECRAWL_API_KEY
export FIRECRAWL_API_KEY# PowerShell: nhập ẩn rồi lấy giá trị ra đưa vào biến môi trường
$secure = Read-Host "FIRECRAWL_API_KEY" -AsSecureString
$env:FIRECRAWL_API_KEY = [System.Net.NetworkCredential]::new('', $secure).PasswordKhi kiểm tra cấu hình, cần làm sao để giá trị không hiện ra trên màn hình, trong log hay trên ảnh chụp màn hình. Để trả lời câu hỏi "đã đặt hay chưa", hai dòng là đủ:
# bash
[ -n "$FIRECRAWL_API_KEY" ] && echo "đã đặt" || echo "chưa đặt"# PowerShell
if ($env:FIRECRAWL_API_KEY) { "đã đặt" } else { "chưa đặt" }Đừng in độ dài hay mấy ký tự đầu. Việc kiểm tra chẳng được thêm gì, còn một mẩu của khóa thì đã là một phần của bí mật.
Có nhiều cách đặt biến vĩnh viễn, và mỗi hệ điều hành có cách riêng. Lúc này chưa cần tìm hiểu chúng: làm việc cục bộ thì terminal là đủ, còn trên hosting, biến được đặt trong bảng điều khiển của chính hosting đó.
Biến môi trường là cách để không viết bí mật vào mã, chứ không phải công cụ bảo vệ. Giá trị không được mã hóa, và chương trình chạy trong cùng môi trường sẽ đọc được nó thoải mái. Chính vì thế, điều quan trọng là bạn đang cấu hình tiến trình của ai.
Tại sao cần .env và .env.example
Đặt biến bằng tay trong mỗi terminal mới thì chẳng mấy chốc sẽ chán, vì vậy người ta gom giá trị vào tệp văn bản .env. Mỗi dòng trong đó chứa một cặp tên và giá trị.
Có một chi tiết quan trọng hay bị bỏ qua nhất. Bản thân tệp không cấu hình gì cả. Phải có thứ gì đó đọc nó và đưa các giá trị vào môi trường của tiến trình - ứng dụng, thư viện, hoặc launcher chạy chương trình của bạn. Không thể tạo .env rồi mặc định rằng chương trình nào cũng sẽ tự đọc nó. Trong trường hợp của bạn, cái gì nạp tệp này thì nên hỏi cho rõ, chứ đừng đoán.
Bên cạnh nó thường có thêm hai tệp:
# .env - chỉ ở lại trên máy của bạn
FIRECRAWL_API_KEY=fc-EXAMPLE-0000000000# .env.example - mẫu cho những người mở dự án
# giá trị trống, chỉ có tên ở đây
FIRECRAWL_API_KEY=# .gitignore
.env| Tệp | Cần để làm gì | Bên trong là gì |
|---|---|---|
.env | giá trị cục bộ để chạy | tên và giá trị thật, chỉ nằm trên máy tính của bạn |
.env.example | gợi ý cho người mở dự án | các tên như trên với giá trị trống, đôi khi kèm chú thích ngắn |
.gitignore | quy tắc để tệp cục bộ không lọt vào repository | một dòng chứa tên tệp |
Lý do tách ra rất đơn giản. .env.example trả lời câu hỏi "dự án này cần những biến nào" và cứ yên tâm nằm trong repository, vì trong đó không có giá trị nào. Khóa thật không bao giờ được chép vào nó, kể cả tạm thời.
Nếu bạn chưa quen với Git, ở đây chỉ cần nhớ vai trò của .gitignore. Nó liệt kê các tệp không cần đưa vào repository. Cách làm việc với Git và .gitignore sẽ được phân tích chi tiết ở cấp 13.
Còn bây giờ là một cái bẫy với .gitignore mà người ta vấp phải thường xuyên. Quy tắc chỉ tác dụng với các tệp Git chưa theo dõi. Nếu .env đã từng được commit, thêm một dòng vào .gitignore cũng không gỡ được nó ra. Tệp vẫn nằm dưới quản lý phiên bản, và các thay đổi của nó vẫn tiếp tục lọt vào commit. Tài liệu Git nói thẳng điều này. Các quy tắc ignore không tác động đến những tệp Git đã theo dõi. Tài liệu gitignore.
Điều kiện chính nằm ở giữa sơ đồ. Cùng một dòng trong .gitignore chỉ có tác dụng khi .env chưa được theo dõi. Với tệp đã được theo dõi, cần thêm một bước riêng.
Vì vậy, trước khi ghi bí mật thật vào tệp, nên chắc chắn rằng Git không biết đến nó. git status sẽ cho thấy tệp có nằm trong danh sách thay đổi hay không. Nếu tệp đã được theo dõi, hãy gỡ nó khỏi theo dõi bằng lệnh git rm --cached .env và commit thay đổi này - tệp vẫn nằm trên đĩa nhưng ra khỏi quản lý phiên bản.
Chỉ lưu ý rằng điều này chỉ ảnh hưởng đến các commit sau này. Giá trị đã lọt vào lịch sử sẽ vẫn nằm đó, và phải xử lý nó theo cách khác - về việc này xem bên dưới.
Cách truyền khóa cho kết nối MCP
MCP là cách kết nối một công cụ bên ngoài với Claude Code. Kết nối được mô tả trong tệp .mcp.json, và tệp này thường nằm trong repository cùng với dự án, tức là cả nhóm đều thấy nó. Vậy thì trong đó không được có bí mật.
Kết nối tối thiểu của dịch vụ Firecrawl qua HTTP trông như thế này:
{
"mcpServers": {
"firecrawl": {
"type": "http",
"url": "https://mcp.firecrawl.dev/v2/mcp",
"headers": {
"Authorization": "Bearer ${FIRECRAWL_API_KEY}"
}
}
}
}Trong tệp không có khóa. Header chứa ${FIRECRAWL_API_KEY} - tham chiếu đến biến môi trường theo tên. Claude Code điền vào đây giá trị lấy từ môi trường của tiến trình mà nó đang chạy trong đó. Vào repository chỉ có tên, còn giá trị thì mỗi người giữ của riêng mình. Claude Code thực hiện được phép thay thế này trong các trường url, headers, env, command và args. Claude Code về MCP.
Sơ đồ cho thấy trong header của yêu cầu có hai thứ đến từ hai nơi khác nhau gặp nhau. Mẫu header đến từ cấu hình chung, còn giá trị được điền từ môi trường tiến trình. Không ở bước nào khóa bị ghi vào tệp sẽ lên repository.
Địa chỉ và cách truyền khóa được lấy từ tài liệu của chính dịch vụ. Với Firecrawl, đó là header Authorization, và tài liệu còn nói riêng rằng không nên đặt khóa vào địa chỉ MCP. Lý do đơn giản. Địa chỉ là văn bản thường, nó lọt vào cấu hình, log và lịch sử lệnh, và khóa cũng sẽ lọt vào đó theo.
Một số dịch vụ hỗ trợ đăng nhập qua trình duyệt. Máy khách tự mở trang đăng nhập, và bạn hoàn toàn không phải nhập API key bằng tay. Firecrawl có một địa chỉ riêng cho điều này. Tài liệu Firecrawl về MCP.
Nếu công cụ không phản hồi, hãy kiểm tra theo thứ tự:
- tên biến trong cấu hình và tên bạn đã đặt khớp nhau đến từng ký tự;
- giá trị được đặt trong đúng tiến trình đã khởi chạy Claude Code;
- tiến trình đã được khởi động lại sau khi đặt biến.
Khi thiếu biến, Claude Code không âm thầm thất bại. Nó vẫn tải cấu hình, giữ nguyên chuỗi ${FIRECRAWL_API_KEY} không thay thế và ghi cảnh báo kèm tên biến bị thiếu. Bản thân giá trị không bị in ra ở đâu cả, nên việc chẩn đoán là an toàn. In toàn bộ môi trường ra để tìm lỗi thì chắc chắn không cần.
Ví dụ này dùng để hiểu một kết nối đã có sẵn. Chọn máy chủ thế nào, thêm vào ra sao và nó nhận những quyền gì là một chủ đề riêng; ở đây chỉ quan trọng một điều: bí mật nằm ở đâu trong cấu trúc này.
Nếu khóa đã bị lộ ra ngoài
Chuyện này khá thường gặp. Khóa lọt vào một tin nhắn, một commit hay một ảnh chụp màn hình. Không cần hoảng, nhưng cũng đừng trì hoãn. Thứ tự xử lý như sau:
- thu hồi hoặc thay khóa trong trang tài khoản của dịch vụ. Đây là bước duy nhất khiến chuỗi cũ không còn mở được gì nữa;
- đặt giá trị mới ở những nơi cần nó: trên máy cục bộ, trên hosting, trong cấu hình kết nối;
- xử lý nơi khóa bị lộ - xóa tin nhắn, sửa tệp, đóng quyền truy cập;
- xem trong trang tài khoản khóa đã được dùng như thế nào và dịch vụ ghi nhận chi phí ra sao.
Thứ tự là đúng như vậy, và bước đầu tiên không được đổi chỗ. Xóa tin nhắn hay thêm commit mới đè lên commit cũ không làm cho bí mật cũ an toàn trở lại. Tin nhắn có thể đã bị đọc, repository đã bị clone, lịch sử đã được lưu lại. Chừng nào khóa còn hiệu lực, nó còn dùng được với bất kỳ ai đang giữ nó.
Riêng về repository. Nếu bí mật đã lọt vào commit, GitHub có hướng dẫn xóa dữ liệu như vậy. Xóa dữ liệu nhạy cảm khỏi repository. Nhưng hãy nhớ rằng dọn sạch hoàn toàn lịch sử sẽ viết lại các commit đã có, tức là ảnh hưởng đến tất cả những ai đang làm việc với repository đó. Việc này cần thống nhất với chủ repository, chứ không tự làm một mình theo một bài viết hướng dẫn. Còn thay khóa thì làm ngay, không cần hỏi ý ai.
Những gì nên giao cho Claude Code
Agent lo được gần như toàn bộ phần cơ chế trong bài này, trừ một việc. Bí mật thì bạn tự nhập. Những việc hợp lý để giao cho nó:
- chuẩn bị cấu hình với tên biến, không phải giá trị;
- nói rõ cái gì nạp biến vào tiến trình và biến phải có mặt trong tiến trình nào;
- kiểm tra để chắc chắn bí mật không có trong bản build phía client và trong các tệp Git đang theo dõi;
- chạy kiểm tra truy cập và báo kết quả mà không in giá trị ra.
Phần của bạn là nhập khóa thật qua đúng cơ chế dành cho việc đó: nhập ẩn trong terminal, tệp cục bộ, bảng điều khiển của hosting. Bí mật không bao giờ được đưa vào cuộc trò chuyện, ảnh chụp màn hình hay báo cáo của agent, kể cả "chỉ một giây để kiểm tra".
Yêu cầu có thể viết đại loại như sau:
Kết nối dịch vụ qua API. Khóa tôi sẽ tự đặt.
Lấy từ tài liệu địa chỉ, phương thức và cách truyền khóa.
Trong cấu hình chỉ ghi tên biến môi trường, không ghi giá trị.
Cho tôi biết tiến trình nào phải nhận biến và cái gì nạp nó vào.
Kiểm tra để chắc chắn khóa không có trong bản build phía client và trong các tệp Git đang theo dõi.
Kiểm tra truy cập mà không in ra giá trị hay bất kỳ phần nào của nó.
Báo kết quả: đã cấu hình, chưa đặt, hoặc truy cập bị từ chối.Điều cuối cùng, về cách đọc báo cáo. "Biến đã được đặt" và "truy cập hoạt động" là hai khẳng định khác nhau, đừng lấy cái này thay cho cái kia. Thiếu giá trị cũng khác với việc dịch vụ từ chối. Trường hợp đầu, yêu cầu không có gì để ký, trường hợp sau, dịch vụ đã trả lời nhưng không cấp quyền. Nếu agent chỉ nói "xong rồi", hãy hỏi lại chính xác nó đã kiểm tra những gì.
Không gửi bí mật vào cuộc trò chuyện, không để lộ trên ảnh chụp màn hình và không chèn vào tệp sẽ lên repository. Mọi thứ khác trong bài này là những cách giữ đúng quy tắc đó mà vẫn không cản trở công việc.
GO TO FULL VERSION