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 SecretVí dụRủi ro nếu bị lộ
API KeyOpenAI, DataForSEO, AnthropicSử dụng trái phép, phát sinh phí
Database CredentialSupabase service role keyTruy cập toàn bộ dữ liệu
Auth SecretJWT secret, session keyChiếm đoạt tài khoản
Third-party TokenGitHub, Railway tokenTruy 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:

  1. Vào project trên Railway
  2. Click vào service
  3. Vào tab "Variables"
  4. 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ườngSecret Ở Đâu
LocalFile .env.local
StagingRailway staging service
ProductionRailway 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ệnHành động
Thành viên rời teamRotate secret họ có quyền truy cập
Nghi ngờ bị lộRotate ngay lập tức
Secret xuất hiện trong logRotate ngay lập tức
Hàng nămThự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

  1. Tạo secret mới trong dashboard của provider
  2. Cập nhật trên Railway (hoặc nơi lưu trữ secret khác)
  3. Kiểm tra ứng dụng hoạt động với secret mới
  4. Thu hồi secret cũ trong dashboard của provider
  5. 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:

  1. Rotate secret ngay lập tức (cái cũ coi như đã bị lộ)
  2. Xóa khỏi code
  3. Thêm vào .gitignore
  4. Cân nhắc dùng git-filter-repo để dọn lịch sử

Phòng ngừa:

  • Dùng .env.example là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ànAn toàn
Slack/EmailPassword manager
Shared docRailway variable
ScreenshotFile đượ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ọngProduction DB, payment APITruy cập tối thiểu, lịch rotation
Nhạy cảmAI API key, auth secretTruy cập team, tách biệt môi trường
Tiêu chuẩnAnalytics, loggingTruy 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

FileCommit?Chứa gì
.env.exampleChỉ tên biến
.env.localKhôngSecret local dev
.env.productionKhôngNếu dùng, secret production

Khi Secret Bị Lộ -- Làm Gì?

  1. Giả định đã bị lộ -- đừng hy vọng "chắc không ai thấy đâu"
  2. Rotate ngay lập tức -- lấy secret mới từ provider
  3. Cập nhật mọi nơi -- Railway, local, mọi bản sao
  4. Thu hồi secret cũ -- trong dashboard provider
  5. Kiểm tra -- xem có sử dụng trái phép không

Quản Lý Key Theo Provider

ProviderQuản lý key ở đâu
OpenAIplatform.openai.com -> API Keys
Anthropicconsole.anthropic.com -> API Keys
SupabaseProject Settings -> API
DataForSEOapp.dataforseo.com -> API Access
RailwayProject -> Variables