A production-inspired REST API built with ASP.NET Core that demonstrates how real SaaS platforms (Stripe, OpenAI) protect their APIs with key-based authentication, rate limiting, and usage analytics.
- ASP.NET Core 10 — REST API, custom middleware pipeline
- Clean Architecture — Domain, Application, Infrastructure, API
- Entity Framework Core + SQL Server — persistence, EF Fluent API
- Redis — cache-aside key validation, sliding window rate limiting
- ASP.NET Core Identity + JWT — user authentication
- Channel<T> + BackgroundService — non-blocking usage logging
Domain → Application → Infrastructure → API
Four layers, strict dependency rules. Business logic never touches the database. Every business rule is testable without spinning up a database or Redis.
ApiKeyManagement/
├── API/ # Controllers, Middlewares, Program.cs
├── Application/ # CQRS, Interfaces, DTOs
├── Domain/ # Entities, Exceptions
└── Infrastructure/ # EF Core DbContext, Redis Services, Identity
- .NET 10 SDK
- SQL Server (or Docker container)
- Redis (or Docker container)
-
Clone the repository
git clone https://github.com/Haitham-AbdelKarim/ApiKeyManagement.git cd ApiKeyManagement -
Configuration Update the
appsettings.jsonfile in theAPIproject with your connection strings for SQL Server and Redis, as well as your JWT configuration. -
Database Migrations Navigate to the API folder and run the EF Core migrations to create the database schema:
cd API dotnet ef database update -
Run the Application
dotnet run
The API will start. Navigate to
https://localhost:{port}/swagger(or the port specified inlaunchSettings.json) to explore the interactive API documentation.
Two completely separate authentication flows:
JWT (Bearer token) → manage your account
POST /api/auth/register
POST /api/auth/login
POST /api/keys
GET /api/keys
DELETE /api/keys/{id}
GET /api/usage/summary
GET /api/usage/history
API Key (X-Api-Key) → call protected endpoints
GET /api/v1/status
POST /api/v1/text/analyze
Every request to /api/v1/ passes through three middleware classes before reaching any controller:
ApiKeyExtractionMiddleware → reads header, SHA-256 hashes it
ApiKeyValidationMiddleware → Redis cache-aside → SQL fallback
RateLimitingMiddleware → atomic sliding window via Lua script
UsageLoggingMiddleware → logs after response (non-blocking)
-
Get a JWT Token:
curl -X POST https://localhost:7001/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"user@example.com", "password":"Password123!"}'
-
Generate an API Key:
curl -X POST https://localhost:7001/api/keys \ -H "Authorization: Bearer <YOUR_JWT_TOKEN>" \ -H "Content-Type: application/json" \ -d '{"name": "Production Key"}'
-
Call a Protected Endpoint:
curl -X GET https://localhost:7001/api/v1/status \ -H "X-Api-Key: <YOUR_RAW_API_KEY>"
API keys are never stored — only their SHA-256 hash. The raw key is returned once on creation and discarded. A database breach exposes only hashes.
Redis cache-aside — validated keys are cached for 5 minutes. Cache hits bypass SQL Server entirely. Revocation immediately invalidates the cache entry.
Lua script for rate limiting — runs atomically inside Redis. Prevents the race condition that makes C# locks and Redis transactions ineffective across multiple servers.
Non-blocking usage logging — Channel<UsageRecord> decouples the middleware (producer) from the background worker (consumer). The caller never waits for a DB write.
| Plan | Requests/min | Requests/month | Max Keys |
|---|---|---|---|
| Free | 10 | 1,000 | 3 |
| Pro | 60 | 50,000 | 10 |
| Enterprise | 300 | 500,000 | 50 |
Rate limit headers are attached to every response:
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1746529260
- Hangfire scheduled jobs (monthly reset, quota alerts)
- Unit and integration tests (xUnit)
- Email notifications at 80% quota
- IP allowlisting per key
- Azure deployment