--- story_id: story_5_2 sprint_id: sprint_5 phase: Phase 1 - Foundation status: not_started priority: P0 story_points: 5 assignee: backend estimated_days: 2 created_date: 2025-11-06 dependencies: [] --- # Story 5.2: API Key Management System **Phase**: Phase 1 - Foundation (Week 1-2) **Priority**: P0 CRITICAL **Estimated Effort**: 5 Story Points (2 days) ## User Story **As a** System Administrator **I want** to securely manage API Keys for AI agents **So that** only authorized AI agents can access ColaFlow through MCP protocol ## Business Value Security is critical for M2. API Key management ensures only authorized AI agents can read/write ColaFlow data. Without this, MCP Server would be vulnerable to unauthorized access. **Impact**: - Prevents unauthorized AI access - Enables multi-tenant security isolation - Supports audit trail for all AI operations - Foundation for rate limiting and IP whitelisting ## Acceptance Criteria ### AC1: API Key Generation - [ ] Generate unique 40-character random API Keys - [ ] Format: `cola_` prefix + 36 random characters (e.g., `cola_abc123...xyz`) - [ ] API Keys are cryptographically secure (using `RandomNumberGenerator`) - [ ] No duplicate keys in database (unique constraint) ### AC2: Secure Storage - [ ] API Key hashed with BCrypt before storage - [ ] Only first 8 characters stored as `key_prefix` for lookup - [ ] Full hash stored in `key_hash` column - [ ] Original API Key never stored (shown once at creation) ### AC3: API Key Validation - [ ] Middleware validates `Authorization: Bearer ` header - [ ] API Key lookup by prefix (fast index) - [ ] Hash verification with BCrypt.Verify - [ ] Expired keys rejected (check `expires_at`) - [ ] Revoked keys rejected (check `revoked_at`) - [ ] Update `last_used_at` and `usage_count` on successful validation ### AC4: Multi-Tenant Isolation - [ ] API Key linked to specific `tenant_id` - [ ] All MCP operations scoped to API Key's tenant - [ ] Cross-tenant access blocked (Global Query Filters) - [ ] TenantContext service extracts tenant from API Key ### AC5: CRUD API Endpoints - [ ] `POST /api/mcp/keys` - Create API Key (returns plain key once) - [ ] `GET /api/mcp/keys` - List tenant's API Keys (no key_hash) - [ ] `GET /api/mcp/keys/{id}` - Get API Key details - [ ] `PATCH /api/mcp/keys/{id}` - Update name/permissions - [ ] `DELETE /api/mcp/keys/{id}` - Revoke API Key (soft delete) ### AC6: Testing - [ ] Unit tests for key generation (uniqueness, format) - [ ] Unit tests for BCrypt hashing/verification - [ ] Integration tests for CRUD operations - [ ] Integration tests for multi-tenant isolation - [ ] Integration tests for authentication middleware ## Technical Design ### Database Schema ```sql CREATE TABLE mcp_api_keys ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, -- Security key_hash VARCHAR(64) NOT NULL UNIQUE, key_prefix VARCHAR(16) NOT NULL, -- Metadata name VARCHAR(255) NOT NULL, description TEXT, -- Permissions (JSONB for flexibility) permissions JSONB NOT NULL DEFAULT '{"read": true, "write": false}', ip_whitelist JSONB, -- Status status INT NOT NULL DEFAULT 1, -- 1=Active, 2=Revoked last_used_at TIMESTAMP NULL, usage_count BIGINT NOT NULL DEFAULT 0, -- Lifecycle created_at TIMESTAMP NOT NULL DEFAULT NOW(), expires_at TIMESTAMP NOT NULL, -- Default: 90 days revoked_at TIMESTAMP NULL, revoked_by UUID REFERENCES users(id), -- Indexes INDEX idx_key_prefix (key_prefix), INDEX idx_tenant_user (tenant_id, user_id), INDEX idx_expires_at (expires_at), INDEX idx_status (status) ); ``` ### Domain Model ```csharp public class McpApiKey : AggregateRoot { public Guid TenantId { get; private set; } public Guid UserId { get; private set; } public string KeyHash { get; private set; } public string KeyPrefix { get; private set; } public string Name { get; private set; } public string? Description { get; private set; } public ApiKeyPermissions Permissions { get; private set; } public List? IpWhitelist { get; private set; } public ApiKeyStatus Status { get; private set; } public DateTime? LastUsedAt { get; private set; } public long UsageCount { get; private set; } public DateTime ExpiresAt { get; private set; } public DateTime? RevokedAt { get; private set; } public Guid? RevokedBy { get; private set; } // Factory method public static (McpApiKey apiKey, string plainKey) Create( string name, Guid tenantId, Guid userId, ApiKeyPermissions permissions, int expirationDays = 90) { var plainKey = GenerateApiKey(); var keyHash = BCrypt.Net.BCrypt.HashPassword(plainKey); var keyPrefix = plainKey.Substring(0, 12); // "cola_abc123..." var apiKey = new McpApiKey { Id = Guid.NewGuid(), TenantId = tenantId, UserId = userId, KeyHash = keyHash, KeyPrefix = keyPrefix, Name = name, Permissions = permissions, Status = ApiKeyStatus.Active, ExpiresAt = DateTime.UtcNow.AddDays(expirationDays) }; apiKey.AddDomainEvent(new ApiKeyCreatedEvent(apiKey.Id, name)); return (apiKey, plainKey); } public void Revoke(Guid revokedBy) { if (Status == ApiKeyStatus.Revoked) throw new InvalidOperationException("API Key already revoked"); Status = ApiKeyStatus.Revoked; RevokedAt = DateTime.UtcNow; RevokedBy = revokedBy; AddDomainEvent(new ApiKeyRevokedEvent(Id, Name)); } public void RecordUsage() { LastUsedAt = DateTime.UtcNow; UsageCount++; } private static string GenerateApiKey() { var bytes = new byte[27]; // 36 chars in base64 using var rng = RandomNumberGenerator.Create(); rng.GetBytes(bytes); return "cola_" + Convert.ToBase64String(bytes) .Replace("+", "").Replace("/", "").Replace("=", "") .Substring(0, 36); } } public class ApiKeyPermissions { public bool Read { get; set; } = true; public bool Write { get; set; } = false; public List AllowedResources { get; set; } = new(); public List AllowedTools { get; set; } = new(); } public enum ApiKeyStatus { Active = 1, Revoked = 2 } ``` ### Authentication Middleware ```csharp public class McpApiKeyMiddleware { private readonly RequestDelegate _next; public async Task InvokeAsync( HttpContext context, IMcpApiKeyService apiKeyService) { // Only apply to /mcp endpoints if (!context.Request.Path.StartsWithSegments("/mcp")) { await _next(context); return; } // Extract API Key from Authorization header var apiKey = ExtractApiKey(context.Request.Headers); if (string.IsNullOrEmpty(apiKey)) { context.Response.StatusCode = 401; await context.Response.WriteAsJsonAsync(new McpError { Code = McpErrorCode.Unauthorized, Message = "Missing API Key" }); return; } // Validate API Key var validationResult = await apiKeyService.ValidateAsync(apiKey); if (!validationResult.IsValid) { context.Response.StatusCode = 401; await context.Response.WriteAsJsonAsync(new McpError { Code = McpErrorCode.Unauthorized, Message = validationResult.ErrorMessage }); return; } // Set TenantContext for downstream handlers context.Items["TenantId"] = validationResult.TenantId; context.Items["ApiKeyId"] = validationResult.ApiKeyId; context.Items["ApiKeyPermissions"] = validationResult.Permissions; await _next(context); } } ``` ## Tasks ### Task 1: Domain Layer - McpApiKey Aggregate (4 hours) - [ ] Create `McpApiKey` entity class - [ ] Create `ApiKeyPermissions` value object - [ ] Implement `Create()` factory method with key generation - [ ] Implement `Revoke()` method - [ ] Create domain events (ApiKeyCreated, ApiKeyRevoked) **Files to Create**: - `ColaFlow.Modules.Mcp.Domain/Entities/McpApiKey.cs` - `ColaFlow.Modules.Mcp.Domain/ValueObjects/ApiKeyPermissions.cs` - `ColaFlow.Modules.Mcp.Domain/Events/ApiKeyCreatedEvent.cs` - `ColaFlow.Modules.Mcp.Domain/Events/ApiKeyRevokedEvent.cs` ### Task 2: Infrastructure - Repository & Database (4 hours) - [ ] Create `IMcpApiKeyRepository` interface - [ ] Implement `McpApiKeyRepository` (EF Core) - [ ] Create database migration for `mcp_api_keys` table - [ ] Configure EF Core entity (fluent API) - [ ] Add indexes (key_prefix, tenant_id) **Files to Create**: - `ColaFlow.Modules.Mcp.Domain/Repositories/IMcpApiKeyRepository.cs` - `ColaFlow.Modules.Mcp.Infrastructure/Repositories/McpApiKeyRepository.cs` - `ColaFlow.Modules.Mcp.Infrastructure/EntityConfigurations/McpApiKeyConfiguration.cs` - `ColaFlow.Modules.Mcp.Infrastructure/Migrations/YYYYMMDDHHMMSS_AddMcpApiKeys.cs` ### Task 3: Application Layer - API Key Service (4 hours) - [ ] Create `IMcpApiKeyService` interface - [ ] Implement `CreateApiKeyAsync()` method - [ ] Implement `ValidateAsync()` method (BCrypt verification) - [ ] Implement `RevokeApiKeyAsync()` method - [ ] Implement `GetApiKeysAsync()` query **Files to Create**: - `ColaFlow.Modules.Mcp.Application/Contracts/IMcpApiKeyService.cs` - `ColaFlow.Modules.Mcp.Application/Services/McpApiKeyService.cs` ### Task 4: Authentication Middleware (3 hours) - [ ] Create `McpApiKeyMiddleware` class - [ ] Extract API Key from `Authorization: Bearer` header - [ ] Validate API Key (call `IMcpApiKeyService.ValidateAsync`) - [ ] Set TenantContext in HttpContext.Items - [ ] Return 401 for invalid/expired keys **Files to Create**: - `ColaFlow.Modules.Mcp/Middleware/McpApiKeyMiddleware.cs` - `ColaFlow.Modules.Mcp/Extensions/McpMiddlewareExtensions.cs` ### Task 5: REST API Endpoints (3 hours) - [ ] Create `McpKeysController` - [ ] `POST /api/mcp/keys` - Create API Key - [ ] `GET /api/mcp/keys` - List API Keys - [ ] `GET /api/mcp/keys/{id}` - Get API Key details - [ ] `DELETE /api/mcp/keys/{id}` - Revoke API Key - [ ] Add [Authorize] attribute (require JWT) **Files to Create**: - `ColaFlow.Modules.Mcp/Controllers/McpKeysController.cs` - `ColaFlow.Modules.Mcp/DTOs/CreateApiKeyRequest.cs` - `ColaFlow.Modules.Mcp/DTOs/ApiKeyResponse.cs` ### Task 6: Unit Tests (3 hours) - [ ] Test API Key generation (format, uniqueness) - [ ] Test BCrypt hashing/verification - [ ] Test McpApiKey domain logic (Create, Revoke) - [ ] Test validation logic (expired, revoked) **Files to Create**: - `ColaFlow.Modules.Mcp.Tests/Domain/McpApiKeyTests.cs` - `ColaFlow.Modules.Mcp.Tests/Services/McpApiKeyServiceTests.cs` ### Task 7: Integration Tests (3 hours) - [ ] Test CRUD API endpoints - [ ] Test multi-tenant isolation (Tenant A cannot see Tenant B keys) - [ ] Test authentication middleware (valid/invalid keys) - [ ] Test expired key rejection **Files to Create**: - `ColaFlow.Modules.Mcp.Tests/Integration/McpApiKeyIntegrationTests.cs` ## Testing Strategy ### Unit Tests (Target: > 80% coverage) - API Key generation logic - BCrypt hashing/verification - Domain entity methods (Create, Revoke) - Validation logic (expiration, revocation) ### Integration Tests - CRUD operations end-to-end - Authentication middleware flow - Multi-tenant isolation verification - Database queries (repository) ### Manual Testing Checklist - [ ] Create API Key → returns plain key once - [ ] Use API Key in Authorization header → success - [ ] Use expired API Key → 401 Unauthorized - [ ] Use revoked API Key → 401 Unauthorized - [ ] Tenant A cannot see Tenant B's keys - [ ] API Key usage count increments ## Dependencies **Prerequisites**: - .NET 9 ASP.NET Core - BCrypt.Net NuGet package - EF Core 9 - PostgreSQL database **Blocks**: - All MCP operations (Story 5.5, 5.11) - Need API Key authentication ## Risks & Mitigation | Risk | Impact | Probability | Mitigation | |------|--------|-------------|------------| | API Key brute force | High | Low | BCrypt hashing (slow), rate limiting | | Key prefix collision | Medium | Very Low | 12-char prefix, crypto RNG | | BCrypt performance | Medium | Low | Prefix lookup optimization | | Key storage security | High | Low | Hash only, audit access | ## Definition of Done - [ ] Code compiles without warnings - [ ] All unit tests passing (> 80% coverage) - [ ] All integration tests passing - [ ] Code reviewed and approved - [ ] Database migration created and tested - [ ] XML documentation for public APIs - [ ] Authentication middleware works end-to-end - [ ] Multi-tenant isolation verified (100%) - [ ] API Key never logged in plain text ## Notes ### Why This Story Matters - **Security Foundation**: All MCP operations depend on secure authentication - **Multi-Tenant Isolation**: Prevents cross-tenant data access - **Audit Trail**: Tracks which AI agent performed which operation - **Compliance**: Supports GDPR, SOC2 requirements ### Key Design Decisions 1. **BCrypt Hashing**: Slow by design, prevents brute force 2. **Prefix Lookup**: Fast lookup without full hash scan 3. **JSONB Permissions**: Flexible permission model for future 4. **Soft Delete**: Revocation preserves audit trail ### Security Best Practices - API Key shown only once at creation - BCrypt with default cost factor (10) - 90-day expiration by default - Rate limiting (to be added in Phase 6) - IP whitelisting (to be added in Phase 4) ### Reference Materials - Sprint 5 Plan: `docs/plans/sprint_5.md` - Architecture Design: `docs/M2-MCP-SERVER-ARCHITECTURE.md` - BCrypt.Net Documentation: https://github.com/BcryptNet/bcrypt.net