Cách Mình Quản Lý Secrets
Environment variable, API key, và cách giữ dữ liệu nhạy cảm an toàn
Secret là chìa khóa của mọi thứ: API key, mật khẩu database, authentication token. Lộ một cái thôi là đủ rắc rối -- từ phát sinh phí cho đến mất toàn bộ dữ liệu khách hàng.
Quy tắc vàng: Không bao giờ commit secret vào Git. Không bao giờ.
Secret Là Gì?
| Loại Secret | Ví dụ | Rủi ro nếu bị lộ |
|---|---|---|
| API Key | OpenAI, DataForSEO, Anthropic | Sử dụng trái phép, phát sinh phí |
| Database Credential | Supabase service role key | Truy cập toàn bộ dữ liệu |
| Auth Secret | JWT secret, session key | Chiếm đoạt tài khoản |
| Third-party Token | GitHub, Railway token | Truy cập hệ thống |
Environment Variable
Lưu secret trong environment variable, không phải trong code. Nghe thì hiển nhiên, nhưng cứ vội là hay quên.
Local Development: .env.local
# .env.local (KHÔNG BAO GIỜ commit file này)
# Database
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_ROLE_KEY=eyJ...
# Dịch vụ AI
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
# API SEO
DATAFORSEO_LOGIN=your@email.com
DATAFORSEO_PASSWORD=your-password
# Ứng dụng
NEXT_PUBLIC_APP_URL=http://localhost:3000
BETTER_AUTH_SECRET=random-32-char-string
.env.example (Commit File Này)
# .env.example (template cho team, AN TOÀN để commit)
# Database
SUPABASE_URL=
SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
# Dịch vụ AI
ANTHROPIC_API_KEY=
OPENAI_API_KEY=
# API SEO
DATAFORSEO_LOGIN=
DATAFORSEO_PASSWORD=
# Ứng dụng
NEXT_PUBLIC_APP_URL=http://localhost:3000
BETTER_AUTH_SECRET=
File này là "bản đồ" cho team biết cần những biến nào, mà không lộ giá trị thật.
.gitignore
Đảm bảo secret không bao giờ vô tình lọt vào Git:
# File environment
.env
.env.local
.env.*.local
# Ngoại lệ: template thì an toàn
!.env.example
Truy Cập Secret Trong Code
NextJS (Server-Side)
// Server component, API route, server action
const apiKey = process.env.ANTHROPIC_API_KEY;
// Validate secret tồn tại khi khởi động
if (!process.env.SUPABASE_URL) {
throw new Error('Missing SUPABASE_URL environment variable');
}
NextJS (Client-Side)
Chỉ biến NEXT_PUBLIC_* mới được phơi ra trình duyệt. Đây là cơ chế bảo vệ của Next.js -- đừng cố vượt qua:
// Có sẵn trong client code
const appUrl = process.env.NEXT_PUBLIC_APP_URL;
// KHÔNG có sẵn trong client (undefined)
const apiKey = process.env.ANTHROPIC_API_KEY; // undefined!
FastAPI (Python)
import os
from dotenv import load_dotenv
load_dotenv() # Load từ file .env
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
if not OPENAI_API_KEY:
raise ValueError("Missing OPENAI_API_KEY")
Secret Trên Production
Railway
Railway có sẵn hệ thống quản lý secret, dùng luôn khỏi tự build:
- Vào project trên Railway
- Click vào service
- Vào tab "Variables"
- Thêm từng secret
Secret trên Railway:
- Được mã hóa khi lưu trữ
- Được inject lúc runtime
- Không bao giờ hiện trong log
- Dùng chung giữa các deployment
Secret Theo Từng Môi Trường
| Môi trường | Secret Ở Đâu |
|---|---|
| Local | File .env.local |
| Staging | Railway staging service |
| Production | Railway production service |
Tuyệt đối không copy secret production về local nếu tránh được. Dùng nhầm môi trường là chuyện "không xảy ra cho đến khi xảy ra."
Rotation Secret
Khi Nào Cần Rotate
| Sự kiện | Hành động |
|---|---|
| Thành viên rời team | Rotate secret họ có quyền truy cập |
| Nghi ngờ bị lộ | Rotate ngay lập tức |
| Secret xuất hiện trong log | Rotate ngay lập tức |
| Hàng năm | Thực hành tốt cho secret quan trọng |
Nguyên tắc: khi nghi ngờ thì cứ rotate. Chi phí rotate thấp hơn nhiều so với chi phí bị lộ.
Checklist Rotation
- Tạo secret mới trong dashboard của provider
- Cập nhật trên Railway (hoặc nơi lưu trữ secret khác)
- Kiểm tra ứng dụng hoạt động với secret mới
- Thu hồi secret cũ trong dashboard của provider
- Kiểm tra xem có bị lộ không -- tìm secret trong log, code
Thứ tự quan trọng: tạo mới trước, xác nhận hoạt động, rồi mới thu hồi cũ. Làm ngược lại là downtime.
Sai Lầm Phổ Biến
Commit secret vào Git
Đây là lỗi "kinh điển" -- và hậu quả thì dai dẳng. Secret một khi vào lịch sử Git là nằm đó mãi mãi, kể cả xóa file đi rồi.
Nếu lỡ commit:
- Rotate secret ngay lập tức (cái cũ coi như đã bị lộ)
- Xóa khỏi code
- Thêm vào .gitignore
- Cân nhắc dùng git-filter-repo để dọn lịch sử
Phòng ngừa:
- Dùng
.env.examplelàm template - Review diff trước khi commit
- Dùng pre-commit hook để quét secret
Log secret
// SAI: Log API key
console.log('Config:', { apiKey: process.env.API_KEY });
// SAI: Lỗi đầy đủ có thể chứa secret
console.error('Failed:', error);
// ĐÚNG: Che giá trị nhạy cảm
console.log('Config:', { apiKey: '***' });
console.error('Failed:', error.message); // Chỉ message, không phải toàn bộ object
Hardcode secret
// SAI: Secret trong code
const client = new OpenAI({ apiKey: 'sk-abc123...' });
// ĐÚNG: Từ environment
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
Có vẻ nhanh hơn khi hardcode lúc dev, nhưng cái "nhanh" đó sẽ quên xóa -- rồi commit luôn lên Git.
Chia sẻ secret không an toàn
| Không an toàn | An toàn |
|---|---|
| Slack/Email | Password manager |
| Shared doc | Railway variable |
| Screenshot | File được mã hóa |
Gửi secret qua Slack thì tiện, nhưng Slack lưu lịch sử mãi mãi. Ai có access vào workspace là thấy hết.
Dùng secret production ở local
Vô tình thay đổi dữ liệu production lúc đang dev -- nghe thì khó xảy ra, nhưng chỉ cần một lần nhầm query là đủ hối hận.
Hầu hết provider cho phép tạo nhiều key -- tạo key riêng cho development, tách biệt hẳn.
Phân Cấp Secret
Không phải secret nào cũng quan trọng như nhau. Phân loại để biết cái nào cần bảo vệ chặt hơn:
| Mức độ | Ví dụ | Bảo vệ |
|---|---|---|
| Quan trọng | Production DB, payment API | Truy cập tối thiểu, lịch rotation |
| Nhạy cảm | AI API key, auth secret | Truy cập team, tách biệt môi trường |
| Tiêu chuẩn | Analytics, logging | Truy cập rộng hơn chấp nhận được |
Validate Khi Khởi Động
Thiếu secret mà đến lúc user gọi API mới phát hiện thì muộn. Fail ngay khi khởi động cho đỡ đau đầu:
// lib/config.ts
function getEnvVar(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
}
export const config = {
supabase: {
url: getEnvVar('SUPABASE_URL'),
anonKey: getEnvVar('SUPABASE_ANON_KEY'),
serviceRoleKey: getEnvVar('SUPABASE_SERVICE_ROLE_KEY'),
},
anthropic: {
apiKey: getEnvVar('ANTHROPIC_API_KEY'),
},
// ...
};
Deploy mà thiếu biến thì app crash ngay, thay vì chạy nửa vời rồi lỗi lung tung.
Secret Trong CI/CD
GitHub Actions
Lưu secret trong phần cài đặt repository GitHub:
# .github/workflows/deploy.yml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
run: railway up
Không Bao Giờ Echo Secret
# SAI: In secret ra log
- run: echo ${{ secrets.API_KEY }}
# ĐÚNG: Dùng mà không in ra
- run: some-command --key ${{ secrets.API_KEY }}
GitHub Actions có mask secret trong log, nhưng đừng dựa vào đó. Cẩn tắc vô áy.
Checklist Đánh Giá
Quản lý secret đang ổn nếu:
- Không có secret trong lịch sử Git
- .env.local đã được gitignore
- .env.example liệt kê tất cả biến cần thiết
- Secret production nằm trên Railway, không phải file
- Ứng dụng fail nhanh nếu thiếu secret
- Secret không bao giờ xuất hiện trong log
- Biết cách rotate secret bị lộ
Quản lý secret cần xem lại nếu:
- Secret nằm trong code hoặc Git
- Team chia sẻ secret qua Slack
- Không có .env.example
- Dùng chung secret cho dev và prod
- Không chắc secret được lưu ở đâu
- Không có quy trình rotation
Tham Khảo Nhanh
Quy Tắc File Environment
| File | Commit? | Chứa gì |
|---|---|---|
.env.example | Có | Chỉ tên biến |
.env.local | Không | Secret local dev |
.env.production | Không | Nếu dùng, secret production |
Khi Secret Bị Lộ -- Làm Gì?
- Giả định đã bị lộ -- đừng hy vọng "chắc không ai thấy đâu"
- Rotate ngay lập tức -- lấy secret mới từ provider
- Cập nhật mọi nơi -- Railway, local, mọi bản sao
- Thu hồi secret cũ -- trong dashboard provider
- Kiểm tra -- xem có sử dụng trái phép không
Quản Lý Key Theo Provider
| Provider | Quản lý key ở đâu |
|---|---|
| OpenAI | platform.openai.com -> API Keys |
| Anthropic | console.anthropic.com -> API Keys |
| Supabase | Project Settings -> API |
| DataForSEO | app.dataforseo.com -> API Access |
| Railway | Project -> Variables |