API Key Authentication
API Key authentication is the simplest authentication method in NitroStack. Perfect for service-to-service communication, internal tools, and rapid prototyping.
๐ฏ When to Use API Keys
โ Perfect For:
- Internal tools and services
- Server-to-server communication
- Development and testing
- Simple authentication requirements
- Quick prototypes
โ Not Ideal For:
- User-facing applications (use OAuth/JWT instead)
- Fine-grained permissions (use scopes with OAuth)
- Temporary access (use short-lived JWTs)
๐ Quick Start
1. Install Template
npx nitrostack init my-api-key-server
# Select: typescript-auth-api-key
cd my-api-key-server
npm install
2. Configure Environment
Edit .env:
# Test API Keys (replace in production!)
API_KEY_1=sk_test_abc123xyz
API_KEY_2=sk_test_def456uvw
3. Start Server
npm run dev
4. Test in Studio
- Open Studio at
http://localhost:3000 - Go to Auth โ API Keys tab
- Enter an API key:
sk_test_abc123xyz - Click "Set Key"
- Go to Tools tab
- Execute a protected tool - it works!
๐ฆ Built-in API Key Module
NitroStack provides ApiKeyModule - a complete, production-ready API key authentication system.
Features
โ
Multiple Keys - Support many API keys
โ
Environment Loading - Auto-load from API_KEY_* env vars
โ
Hashing - Optional SHA-256 hashing for storage
โ
Custom Validation - Add your own validation logic
โ
Key Generation - Built-in secure key generator
โ
Zero Dependencies - Uses Node.js crypto
๐ง Configuration
Basic Setup
// src/app.module.ts
import { Module, ApiKeyModule } from 'nitrostack';
@Module({
name: 'app',
imports: [
// Load API keys from environment variables
ApiKeyModule.forRoot({
keysEnvPrefix: 'API_KEY', // Loads API_KEY_1, API_KEY_2, etc.
}),
],
})
export class AppModule {}
Advanced Configuration
ApiKeyModule.forRoot({
// Load keys from environment variables matching this prefix
keysEnvPrefix: 'API_KEY',
// Or provide keys directly (not recommended for production)
keys: ['sk_test_abc123', 'sk_prod_xyz789'],
// Store keys as hashed values (recommended for production)
hashed: true,
// Custom header name (default: 'x-api-key')
headerName: 'x-api-key',
// Metadata field name (default: 'apiKey')
metadataField: 'apiKey',
// Custom validation logic
customValidation: async (key) => {
// Check against database, rate limits, etc.
const isValid = await db.apiKeys.findOne({ key });
return !!isValid;
},
})
๐ก๏ธ Protecting Tools
Basic Protection
import { Tool, UseGuards, ApiKeyGuard } from 'nitrostack';
export class DemoTools {
// Public tool - no authentication required
@Tool({
name: 'get_public_info',
description: 'Get public information',
})
async getPublicInfo() {
return { message: 'This is public!' };
}
// Protected tool - requires valid API key
@Tool({
name: 'get_protected_data',
description: 'Get protected data',
})
@UseGuards(ApiKeyGuard)
async getProtectedData() {
return { message: 'This is protected!', secret: 'abc123' };
}
}
Access User Context
@Tool({ name: 'check_api_key_status' })
@UseGuards(ApiKeyGuard)
async checkStatus(
@ExecutionContext() context: ExecutionContext
) {
// API key info is populated by ApiKeyGuard
return {
authenticated: true,
subject: context.auth.subject, // 'apikey_sk_test_abc...'
scopes: context.auth.scopes, // ['*'] (full access)
keyPreview: context.auth.subject.substring(0, 20),
};
}
๐ Security Best Practices
1. Generate Secure Keys
import { ApiKeyModule } from 'nitrostack';
// Generate a new API key
const newKey = ApiKeyModule.generateKey('sk');
// โ sk_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
Key Format: {prefix}_{base64url_random_32_bytes}
2. Use Environment Variables
Never commit API keys to Git!
# .env
API_KEY_1=sk_prod_abc123xyz
API_KEY_2=sk_prod_def456uvw
# .env.example (commit this)
API_KEY_1=sk_test_replace_me
API_KEY_2=sk_test_replace_me_too
3. Hash Keys in Database
import { ApiKeyModule } from 'nitrostack';
// Hash a key before storing
const hashedKey = ApiKeyModule.hashKey('sk_prod_abc123xyz');
// โ '5f4dcc3b5aa765d61d8327deb882cf99...'
// Store hashed value in database
await db.apiKeys.create({
keyHash: hashedKey,
userId: 'user123',
createdAt: new Date(),
});
// Configure module to use hashed keys
ApiKeyModule.forRoot({
hashed: true,
customValidation: async (key) => {
const hashedKey = ApiKeyModule.hashKey(key);
const exists = await db.apiKeys.findOne({ keyHash: hashedKey });
return !!exists;
},
});
4. Rotate Keys Regularly
// Implement key rotation
class ApiKeyService {
async rotateKey(oldKey: string): Promise<string> {
// Generate new key
const newKey = ApiKeyModule.generateKey('sk');
// Store new key
await db.apiKeys.create({ key: newKey });
// Revoke old key
await db.apiKeys.delete({ key: oldKey });
return newKey;
}
}
5. Add Rate Limiting
import { RateLimit } from 'nitrostack';
@Tool({ name: 'expensive_operation' })
@UseGuards(ApiKeyGuard)
@RateLimit({ windowMs: 60000, maxRequests: 10 }) // 10 req/min
async expensiveOperation() {
// Rate limited per API key
}
๐ Multi-Auth Patterns
Combine API keys with JWT tokens for flexible authentication.
Either/Or Authentication
Tool accepts either a valid JWT or a valid API key:
import { MultiAuthGuard } from 'nitrostack';
@Tool({ name: 'flexible_access' })
@UseGuards(MultiAuthGuard) // Accepts JWT OR API Key
async flexibleAccess() {
return { message: 'Authenticated with either method!' };
}
Both Required
Tool requires both JWT and API key (extra security):
import { DualAuthGuard } from 'nitrostack';
@Tool({ name: 'critical_operation' })
@UseGuards(DualAuthGuard) // Requires BOTH JWT AND API Key
async criticalOperation() {
return { message: 'Double authenticated!' };
}
๐ก How Keys Are Sent
From Studio
Studio automatically includes the API key in two places:
- Header:
X-API-Key: sk_test_abc123xyz - Metadata:
_meta.apiKey: sk_test_abc123xyz
From Claude/Clients
Method 1: Header (Recommended)
// Client sends
headers: {
'X-API-Key': 'sk_test_abc123xyz'
}
Method 2: Metadata Field
{
"method": "tools/call",
"params": {
"name": "protected_tool",
"arguments": {
"_meta": {
"apiKey": "sk_test_abc123xyz"
}
}
}
}
Custom Header Name
ApiKeyModule.forRoot({
headerName: 'Authorization', // Use Bearer token format
})
// Client sends:
headers: {
'Authorization': 'Bearer sk_test_abc123xyz'
}
๐งช Testing
In Studio
-
Set API Key:
- Go to Auth โ API Keys tab
- Enter key:
sk_test_abc123xyz - Click "Set Key"
-
Test Tools:
- Go to Tools tab
- Execute protected tool
- Key is automatically included!
Programmatic Testing
// test/api-key.test.ts
import { createTestClient } from 'nitrostack/testing';
describe('API Key Authentication', () => {
it('should protect tools with API key', async () => {
const client = await createTestClient(AppModule);
// Without API key - should fail
await expect(
client.callTool('protected_tool', {})
).rejects.toThrow('API key required');
// With API key - should succeed
const result = await client.callTool('protected_tool', {
_meta: { apiKey: 'sk_test_abc123xyz' }
});
expect(result.success).toBe(true);
});
});
๐ Production Deployment
1. Generate Production Keys
# Generate secure production keys
node -e "console.log(require('nitrostack').ApiKeyModule.generateKey('sk'))"
2. Set Environment Variables
# In production environment (Heroku, AWS, etc.)
export API_KEY_PROD_1=sk_prod_...
export API_KEY_PROD_2=sk_prod_...
3. Use Secrets Manager
// Load keys from AWS Secrets Manager, etc.
import { SecretsManager } from 'aws-sdk';
const secrets = new SecretsManager();
const apiKeys = await secrets.getSecretValue({
SecretId: 'mcp-api-keys'
}).promise();
ApiKeyModule.forRoot({
keys: JSON.parse(apiKeys.SecretString),
});
๐ก Common Patterns
Per-User API Keys
// Each user has their own API key
ApiKeyModule.forRoot({
customValidation: async (key) => {
const user = await db.users.findOne({ apiKey: key });
if (!user) return false;
// Store user info in context
context.user = user;
return true;
},
});
Scoped API Keys
// Different keys for different permissions
ApiKeyModule.forRoot({
customValidation: async (key) => {
const keyData = await db.apiKeys.findOne({ key });
if (!keyData) return false;
// Attach scopes to context
context.auth.scopes = keyData.scopes; // ['read', 'write']
return true;
},
});
// Check scopes in tools
@Tool({ name: 'write_data' })
@UseGuards(ApiKeyGuard)
async writeData(@ExecutionContext() context: ExecutionContext) {
if (!context.auth.scopes.includes('write')) {
throw new Error('Insufficient permissions');
}
// ... write data
}
๐ Learn More
Next: Learn about OAuth 2.1 Authentication โ