基于NodeJS与MongoDB的REST API:唯一AppID/AppSecret实现问询
Great question! Let's break this down step by step—since you're aiming to align with industry standards like Facebook, Google, or Twitter, we'll cover everything from uniqueness guarantees to secure generation and storage.
1. Ensuring Unique AppID & AppSecret Combinations
First off, the key here is that your AppID should be globally unique on its own—since each AppSecret is tied to exactly one AppID, enforcing AppID uniqueness automatically ensures the (AppID, AppSecret) pair is unique. Here's how to pull this off:
- Database-Level Enforcement: In MongoDB, add a
unique: trueconstraint to yourappIdfield in the Mongoose schema. This makes MongoDB reject any duplicate AppIDs, so you don't have to worry about accidental duplicates slipping through. - Collision-Resilient Generation: Use generation algorithms with extremely low collision probabilities (like UUIDs or cryptographically secure random strings) to minimize the chance of generating duplicate IDs in the first place. If a collision does occur (though it's highly unlikely), catch the MongoDB duplicate key error and retry generating a new pair.
2. Industry-Standard Characteristics for AppID & AppSecret
Let's map out what makes AppIDs and Secrets fit with big-platform norms:
AppID Features
- Publicly Shareable: It's meant to be used in client-side code (like in API requests) so it doesn't need to be secret.
- Length: Typically 16–32 characters (e.g., Facebook uses 16-digit numeric AppIDs, Google uses longer mixed alphanumeric IDs with a domain suffix).
- Character Set: URL-safe alphanumeric characters (uppercase, lowercase, numbers) — avoid special characters that might cause encoding issues.
- Human-Friendly (Optional): Some platforms use sequential IDs, but random is better for privacy (prevents others from guessing how many apps you have).
AppSecret Features
- Strictly Confidential: Never expose this to clients; it's only used server-side for signing requests or validating credentials.
- High Entropy: Long enough to resist brute-force attacks—aim for 32–64 characters.
- Cryptographically Random: Use secure random number generators (not pseudo-random functions like
Math.random()) to ensure unpredictability. - URL-Safe: Stick to alphanumeric characters plus safe symbols like
-or_(avoid+or/if you're using Base64, or use URL-safe Base64 encoding).
3. JavaScript Implementation & MongoDB Storage
Let's put this into code using Node.js, Mongoose, and the built-in crypto module (no third-party dependencies needed for secure generation).
Step 1: Mongoose Schema Setup
First, define your schema with the unique constraint on appId. We'll also encrypt the appSecret before storing it (never store plaintext secrets!):
const mongoose = require('mongoose'); const crypto = require('crypto'); // Use a secure encryption key stored in environment variables (never hardcode!) const ENCRYPTION_KEY = process.env.APP_SECRET_ENCRYPTION_KEY; const IV_LENGTH = 16; // For AES, this is always 16 bytes // Helper functions to encrypt/decrypt the AppSecret function encrypt(text) { const iv = crypto.randomBytes(IV_LENGTH); const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv); let encrypted = cipher.update(text); encrypted = Buffer.concat([encrypted, cipher.final()]); return `${iv.toString('hex')}:${encrypted.toString('hex')}`; } function decrypt(text) { const [ivHex, encryptedHex] = text.split(':'); const iv = Buffer.from(ivHex, 'hex'); const encryptedText = Buffer.from(encryptedHex, 'hex'); const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv); let decrypted = decipher.update(encryptedText); decrypted = Buffer.concat([decrypted, decipher.final()]); return decrypted.toString(); } const developerAppSchema = new mongoose.Schema({ developerId: { type: mongoose.Schema.Types.ObjectId, ref: 'Developer', required: true }, appId: { type: String, required: true, unique: true, index: true }, appSecret: { type: String, required: true }, appName: { type: String, required: true }, createdAt: { type: Date, default: Date.now } }); // Pre-save hook to encrypt the AppSecret before storing developerAppSchema.pre('save', function(next) { if (this.isModified('appSecret')) { this.appSecret = encrypt(this.appSecret); } next(); }); // Method to decrypt the AppSecret when needed (e.g., for validation) developerAppSchema.methods.getDecryptedSecret = function() { return decrypt(this.appSecret); }; module.exports = mongoose.model('DeveloperApp', developerAppSchema);
Step 2: Generate AppID & AppSecret
Create a utility function to generate secure, compliant pairs:
const crypto = require('crypto'); const DeveloperApp = require('./models/developerApp'); // Generate a random AppID (16 alphanumeric characters) function generateAppId() { return crypto.randomBytes(8).toString('hex'); // 8 bytes = 16 hex chars } // Generate a high-entropy AppSecret (64 characters) function generateAppSecret() { return crypto.randomBytes(32).toString('hex'); // 32 bytes = 64 hex chars } // Create a new app pair with retry on duplicate AppID async function createDeveloperApp(developerId, appName) { let appId, appSecret; let success = false; let app; while (!success) { appId = generateAppId(); appSecret = generateAppSecret(); try { app = await DeveloperApp.create({ developerId, appName, appId, appSecret }); success = true; } catch (err) { // Handle duplicate key error (E11000) if (err.code === 11000 && err.keyPattern.appId) { console.log('Duplicate AppID generated, retrying...'); } else { // Re-throw other errors throw err; } } } // Return the plaintext AppSecret ONCE (only to the developer at creation time!) return { appId: app.appId, appSecret: appSecret, // Don't store this plaintext anywhere else! appName: app.appName, createdAt: app.createdAt }; }
Step 3: Usage Example
When a developer registers their app, call the createDeveloperApp function and return the AppID and plaintext Secret to them (only once—you'll never expose the Secret again):
// Example route handler (Express.js) app.post('/api/developers/:id/apps', async (req, res) => { try { const { id: developerId } = req.params; const { appName } = req.body; const appCredentials = await createDeveloperApp(developerId, appName); res.status(201).json({ message: 'App credentials created successfully', credentials: appCredentials }); } catch (err) { res.status(500).json({ error: err.message }); } });
Key Notes to Remember
- Never Log or Expose the AppSecret: Only send it to the developer once at creation time. Store the encrypted version in your database.
- Use Environment Variables: Keep encryption keys, database URIs, etc., out of code—use
dotenvto manage environment variables. - Rotate Secrets: Add functionality for developers to regenerate their AppSecret if it's compromised.
内容的提问来源于stack exchange,提问作者syedfa

