Using user._id
directly as customerKey
in Toss Payments is not secure because:
- Exposure Risk — If
customerKey
is leaked, attackers can guess or generate valid user IDs. - Predictability — MongoDB ObjectIDs are not cryptographically secure and can be iterated.
- Tampering — A user can manipulate their own
customerKey
if it's predictable.
๐ Secure Solution: Encrypt the User ID
The best approach is to encrypt user._id
when using it as customerKey
, and be able to decrypt it later for verification.
✅ Step 1: Install crypto
for Encryption
First, install the crypto
module if it's not already included in your project:
npm install crypto
✅ Step 2: Create encrypt.js
Utility File
Create a helper function to encrypt and decrypt user._id
securely.
import crypto from 'crypto';
const SECRET_KEY = process.env.ENCRYPTION_KEY || 'your-secure-key-here'; // Use a secure 32-character key
const IV_LENGTH = 16; // Initialization vector must be 16 bytes
export const encrypt = (text) => {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(SECRET_KEY), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return iv.toString('hex') + ':' + encrypted.toString('hex'); // Return as hex string
};
export const decrypt = (text) => {
const textParts = text.split(':');
const iv = Buffer.from(textParts[0], 'hex');
const encryptedText = Buffer.from(textParts[1], 'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(SECRET_KEY), iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
};
๐ Step 3: Use Encrypted customerKey
in Toss Payments
Modify your payment request to use the encrypted user._id
.
import { encrypt } from '../utils/encrypt';
const makePayment = async (user) => {
const customerKey = encrypt(user._id.toString()); // Encrypt user._id
console.log('Encrypted customerKey:', customerKey);
const tossPayments = await loadTossPayments(CLIENT_KEY);
tossPayments.requestBillingAuth('์นด๋', {
customerKey, // Secure customerKey
successUrl: `${window.location.origin}/payment-success`,
failUrl: `${window.location.origin}/payment-fail`,
});
};
✅ Step 4: Decrypt customerKey
When Processing Payments
When handling a payment response from Toss, decrypt the customerKey
to get the real user._id
.
import { decrypt } from '../utils/encrypt';
const processPayment = async (customerKey) => {
const userId = decrypt(customerKey); // Decrypt to get the real user._id
console.log('Decrypted userId:', userId);
// Find user and process payment
const user = await User.findById(userId);
if (!user) {
throw new Error('User not found!');
}
console.log(`Processing payment for ${user.name}`);
};
๐ Why This is Secure?
✅ Unpredictable — The encrypted customerKey
is unique every time.
✅ Cannot Be Guessed – Even if attackers see it, they can't derive the original _id
.
✅ Tamper-Proof – If someone modifies it, decryption fails.
✅ Easy to Implement – Just encrypt before sending, decrypt when receiving.
๐ก Can You Just Use user._id
?
⚠️ NO! Exposing MongoDB _id
is a security risk because:
- Users can guess other valid
_id
s. - If leaked, anyone can impersonate another user.
๐ Final Recommendation
- Always encrypt
user._id
before using it ascustomerKey
. - Decrypt the
customerKey
when processing payments. - Use a secure secret key (
process.env.ENCRYPTION_KEY
).
✅ Best Practice Summary
Method Security Level Notes user._id
(direct use) ❌ Unsafe Predictable, guessable, not secure uuid()
✅ Good Secure, but hard to link payments back to users AES-encrypted user._id
๐ฅ Best Secure, traceable, and unique per user