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 โ