Learned / Security
Security Intermediate

Never Store Secrets in Plain Text

by mobes December 30, 2025 7 views

🎯 Context

I was building an API token system for a community site. Users needed tokens to authenticate API requests from their coding tools (like Claude Code). The initial implementation stored tokens directly in the database as plain VARCHAR fields.

⚠️ The Challenge

The plain-text approach had serious problems:

Anyone with database access could see every user's API token

Tokens were displayed on the website whenever users visited their profile

A single database breach would compromise every user's API access

No way to have multiple tokens or track usage

🔍 What I Tried

First I considered just masking the display (showing ****1234), but realized that's security theater - the token was still stored in plain text. The real fix needed to make retrieval impossible.

The Solution

The fix was to treat API tokens exactly like passwords - store the hash, not the secret.

// Token creation (shown once, then forgotten)
$token = bin2hex(random_bytes(32)); // 64 char hex
$hash = hash('sha256', $token); // Store this
$prefix = substr($token, 0, 12); // For UI display

// Store hash + prefix in database
$stmt = $db->prepare("INSERT INTO api_tokens (user_id, token_hash, token_prefix) VALUES (?, ?, ?)");
$stmt->execute([$userId, $hash, $prefix]);

// Show full token to user ONCE
echo "Your token: $token (copy now - won't be shown again)";

// Token validation (on every API request)
function authenticateToken() {
$token = $_SERVER['HTTP_X_API_TOKEN'] ?? '';
$hash = hash('sha256', $token);

$stmt = $db->prepare("SELECT user_id FROM api_tokens WHERE token_hash = ?");
$stmt->execute([$hash]);
return $stmt->fetch();
}


The key insight: you only need to verify tokens, not retrieve them. Hash on input, compare hashes.

💡 Key Takeaway

Any secret you need to validate (passwords, API tokens, session tokens) should be hashed before storage. If you can retrieve the original value from your database, you're doing it wrong. The pattern is always: generate secret ? show once ? store hash ? compare hashes on validation.

#security #api #authentication #hashing #php
0

Log in to vote

No comments yet. Be the first to share your thoughts!

Log in to participate