Browse Source

Merge pull request #24186 from abpframework/salihozkara/article

Add article on API key management with ABP Framework
pull/24198/head
Alper Ebiçoğlu 3 months ago
committed by GitHub
parent
commit
7286a96ff1
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. BIN
      docs/en/Community-Articles/2025-11-15-building-an-api-key-management-system/coverimage.png
  2. 70
      docs/en/Community-Articles/2025-11-15-building-an-api-key-management-system/images/auth-flow.svg
  3. 354
      docs/en/Community-Articles/2025-11-15-building-an-api-key-management-system/post.md
  4. 1
      docs/en/Community-Articles/2025-11-15-building-an-api-key-management-system/summary.md

BIN
docs/en/Community-Articles/2025-11-15-building-an-api-key-management-system/coverimage.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

70
docs/en/Community-Articles/2025-11-15-building-an-api-key-management-system/images/auth-flow.svg

@ -0,0 +1,70 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 500">
<rect width="700" height="500" fill="#f8f9fa"/>
<text x="350" y="30" text-anchor="middle" font-family="Arial" font-size="20" font-weight="bold" fill="#2c3e50">
API Key Authentication Flow
</text>
<!-- Step 1 -->
<rect x="50" y="70" width="180" height="60" rx="8" fill="#3498db" stroke="#2980b9" stroke-width="2"/>
<text x="140" y="95" text-anchor="middle" font-family="Arial" font-size="13" font-weight="bold" fill="white">1. Client Request</text>
<text x="140" y="115" text-anchor="middle" font-family="Arial" font-size="10" fill="white">X-Api-Key: prefix_key</text>
<path d="M 230,100 L 260,100" stroke="#34495e" stroke-width="2" fill="none"/>
<path d="M 260,100 L 255,95 M 260,100 L 255,105" stroke="#34495e" stroke-width="2" fill="none"/>
<!-- Step 2 -->
<rect x="260" y="70" width="180" height="60" rx="8" fill="#e74c3c" stroke="#c0392b" stroke-width="2"/>
<text x="350" y="95" text-anchor="middle" font-family="Arial" font-size="13" font-weight="bold" fill="white">2. Extract API Key</text>
<text x="350" y="115" text-anchor="middle" font-family="Arial" font-size="10" fill="white">From Header/Query</text>
<path d="M 350,130 L 350,160" stroke="#34495e" stroke-width="2" fill="none"/>
<path d="M 350,160 L 345,155 M 350,160 L 355,155" stroke="#34495e" stroke-width="2" fill="none"/>
<!-- Step 3 -->
<rect x="260" y="160" width="180" height="60" rx="8" fill="#9b59b6" stroke="#8e44ad" stroke-width="2"/>
<text x="350" y="185" text-anchor="middle" font-family="Arial" font-size="13" font-weight="bold" fill="white">3. Lookup by Prefix</text>
<text x="350" y="205" text-anchor="middle" font-family="Arial" font-size="10" fill="white">Cache → Database</text>
<path d="M 350,220 L 350,250" stroke="#34495e" stroke-width="2" fill="none"/>
<path d="M 350,250 L 345,245 M 350,250 L 355,245" stroke="#34495e" stroke-width="2" fill="none"/>
<!-- Step 4 -->
<rect x="260" y="250" width="180" height="60" rx="8" fill="#f39c12" stroke="#e67e22" stroke-width="2"/>
<text x="350" y="275" text-anchor="middle" font-family="Arial" font-size="13" font-weight="bold" fill="white">4. Verify Hash</text>
<text x="350" y="295" text-anchor="middle" font-family="Arial" font-size="10" fill="white">SHA256 + Expiration</text>
<!-- Decision -->
<path d="M 350,310 L 350,330" stroke="#34495e" stroke-width="2" fill="none"/>
<path d="M 350,330 L 345,325 M 350,330 L 355,325" stroke="#34495e" stroke-width="2" fill="none"/>
<path d="M 350,330 L 400,370 L 350,410 L 300,370 Z" fill="#16a085" stroke="#138f75" stroke-width="2"/>
<text x="350" y="378" text-anchor="middle" font-family="Arial" font-size="12" font-weight="bold" fill="white">Valid?</text>
<!-- Success Path -->
<path d="M 400,370 L 500,370" stroke="#2ecc71" stroke-width="3" fill="none"/>
<path d="M 500,370 L 495,365 M 500,370 L 495,375" stroke="#2ecc71" stroke-width="3" fill="none"/>
<text x="450" y="365" text-anchor="middle" font-family="Arial" font-size="11" fill="#27ae60" font-weight="bold">Yes</text>
<rect x="500" y="340" width="150" height="60" rx="8" fill="#2ecc71" stroke="#27ae60" stroke-width="2"/>
<text x="575" y="365" text-anchor="middle" font-family="Arial" font-size="13" font-weight="bold" fill="white">200 OK</text>
<text x="575" y="385" text-anchor="middle" font-family="Arial" font-size="10" fill="white">ClaimsPrincipal</text>
<!-- Failure Path -->
<path d="M 300,370 L 200,370" stroke="#e74c3c" stroke-width="3" fill="none"/>
<path d="M 200,370 L 205,365 M 200,370 L 205,375" stroke="#e74c3c" stroke-width="3" fill="none"/>
<text x="250" y="365" text-anchor="middle" font-family="Arial" font-size="11" fill="#c0392b" font-weight="bold">No</text>
<rect x="50" y="340" width="150" height="60" rx="8" fill="#e74c3c" stroke="#c0392b" stroke-width="2"/>
<text x="125" y="365" text-anchor="middle" font-family="Arial" font-size="13" font-weight="bold" fill="white">401 Unauthorized</text>
<text x="125" y="385" text-anchor="middle" font-family="Arial" font-size="10" fill="white">Invalid/Expired</text>
<!-- Performance Note -->
<rect x="50" y="430" width="600" height="50" rx="8" fill="#ecf0f1" stroke="#bdc3c7" stroke-width="2"/>
<text x="350" y="452" text-anchor="middle" font-family="Arial" font-size="11" fill="#34495e">
⚡ Cache-first strategy ensures ~95% requests skip database lookup
</text>
<text x="350" y="468" text-anchor="middle" font-family="Arial" font-size="10" fill="#7f8c8d">
Typical response time: &lt;5ms (cached) | &lt;50ms (database lookup)
</text>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

354
docs/en/Community-Articles/2025-11-15-building-an-api-key-management-system/post.md

@ -0,0 +1,354 @@
# Building an API Key Management System with ABP Framework
API keys are one of the most common authentication methods for APIs, especially for machine-to-machine communication. In this article, I'll explain what API key authentication is, when to use it, and how to implement a complete API key management system using ABP Framework.
## What is API Key Authentication?
An API key is a unique identifier used to authenticate requests to an API. Unlike user credentials (username/password) or OAuth tokens, API keys are designed for:
- **Programmatic access** - Scripts, CLI tools, and automated processes
- **Service-to-service communication** - Microservices authenticating with each other
- **Third-party integrations** - External systems accessing your API
- **IoT devices** - Embedded systems with limited authentication capabilities
- **Mobile/Desktop apps** - Native applications that need persistent authentication
## Why Use API Keys?
While modern authentication methods like OAuth2 and JWT are excellent for user authentication, API keys offer distinct advantages in certain scenarios:
**Simplicity**: No complex OAuth flows or token refresh mechanisms. Just include the key in your request header.
**Long-lived**: Unlike JWT tokens that expire in minutes/hours, API keys can remain valid for months or years, making them ideal for automated systems.
**Revocable**: You can instantly revoke a compromised key without affecting user credentials.
**Granular Control**: Different keys for different purposes (read-only, admin, specific services).
## Real-World Use Cases
Here are some practical scenarios where API key authentication shines:
### 1. Mobile Applications
Your mobile app needs to call your backend APIs. Instead of storing user credentials or managing token refresh flows, use an API key.
```csharp
// Mobile app configuration
var apiClient = new ApiClient("https://api.yourapp.com");
apiClient.SetApiKey("sk_mobile_prod_abc123...");
```
### 2. Microservice Communication
Service A needs to call Service B's protected endpoints.
```csharp
// Order Service calling Inventory Service
var request = new HttpRequestMessage(HttpMethod.Get, "https://inventory-service/api/products");
request.Headers.Add("X-Api-Key", _configuration["InventoryService:ApiKey"]);
```
### 3. Third-Party Integrations
You're providing APIs to external partners or customers.
```bash
# Customer's integration script
curl -H "X-Api-Key: pk_partner_xyz789..." \
https://api.yourplatform.com/api/orders
```
## Implementing API Key Management in ABP Framework
Now let's see how to build a complete API key management system using ABP Framework. I've created an open-source implementation that you can use in your projects.
### Project Overview
The implementation consists of:
- **User-based API keys** - Each key belongs to a specific user
- **Permission delegation** - Keys inherit user permissions with optional restrictions
- **Secure storage** - Keys are hashed with SHA-256
- **Prefix-based lookup** - Fast key resolution with caching
- **Web UI** - Manage keys through a user-friendly interface
- **Multi-tenancy support** - Full ABP multi-tenancy compatibility
![API Keys Management UI](https://raw.githubusercontent.com/salihozkara/AbpApikeyManagement/refs/heads/master/docs/images/api-keys.png)
### Architecture Overview
The solution follows ABP's modular architecture with four main layers:
```
┌─────────────────────────────────────────────┐
│ Web Layer (UI) │
│ • Razor Pages for CRUD operations │
│ • JavaScript for client interactions │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ AspNetCore Layer (Middleware) │
│ • Authentication Handler │
│ • API Key Resolver (Header/Query) │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ Application Layer (Business Logic) │
│ • ApiKeyAppService (CRUD operations) │
│ • DTO mappings and validations │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ Domain Layer (Core Business) │
│ • ApiKey Entity & Manager │
│ • IApiKeyRepository │
│ • Domain services & events │
└─────────────────────────────────────────────┘
```
### Key Components
#### 1. Domain Layer - The Core Entity
```csharp
public class ApiKey : FullAuditedAggregateRoot<Guid>, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
public virtual Guid UserId { get; protected set; }
public virtual string Name { get; protected set; }
public virtual string Prefix { get; protected set; }
public virtual string KeyHash { get; protected set; }
public virtual DateTime? ExpiresAt { get; protected set; }
public virtual bool IsActive { get; protected set; }
// Key format: {prefix}_{key}
// Only the hash is stored, never the actual key
}
```
**Key Design Decisions:**
- **Prefix-based lookup**: Keys have format `prefix_actualkey`. The prefix is indexed for fast database lookups.
- **SHA-256 hashing**: The actual key is hashed and never stored in plain text.
- **User association**: Each key belongs to a user, inheriting their permissions.
- **Soft delete**: Deleted keys are marked as deleted but not removed from database for audit purposes.
#### 2. Authentication Flow
Here's how authentication works when a request arrives:
![Authentication Flow](images/auth-flow.svg)
```csharp
// 1. Extract API key from request
var apiKey = httpContext.Request.Headers["X-Api-Key"].FirstOrDefault();
if (string.IsNullOrEmpty(apiKey)) return AuthenticateResult.NoResult();
// 2. Split prefix and key
var parts = apiKey.Split('_', 2);
var prefix = parts[0];
var key = parts[1];
// 3. Find key by prefix (cached)
var apiKeyEntity = await _apiKeyRepository.FindByPrefixAsync(prefix);
if (apiKeyEntity == null) return AuthenticateResult.Fail("Invalid API key");
// 4. Verify hash
var keyHash = HashHelper.ComputeSha256(key);
if (apiKeyEntity.KeyHash != keyHash)
return AuthenticateResult.Fail("Invalid API key");
// 5. Check expiration and active status
if (apiKeyEntity.ExpiresAt < DateTime.UtcNow || !apiKeyEntity.IsActive)
return AuthenticateResult.Fail("API key expired or inactive");
// 6. Create claims principal with user identity
var claims = new List<Claim>
{
new Claim(AbpClaimTypes.UserId, apiKeyEntity.UserId.ToString()),
new Claim(AbpClaimTypes.TenantId, apiKeyEntity.TenantId?.ToString() ?? ""),
new Claim("ApiKeyId", apiKeyEntity.Id.ToString())
};
return AuthenticateResult.Success(ticket);
```
#### 3. Creating and Managing API Keys
**Creating a new key:**
![Create API Key Modal](https://raw.githubusercontent.com/salihozkara/AbpApikeyManagement/refs/heads/master/docs/images/new-api-key.png)
```csharp
public class ApiKeyManager : DomainService
{
public async Task<(ApiKey, string)> CreateAsync(
Guid userId,
string name,
DateTime? expiresAt = null)
{
// Generate unique prefix
var prefix = await GenerateUniquePrefixAsync();
// Generate secure random key
var key = GenerateSecureRandomString(32);
// Hash the key for storage
var keyHash = HashHelper.ComputeSha256(key);
var apiKey = new ApiKey(
GuidGenerator.Create(),
userId,
name,
prefix,
keyHash,
expiresAt,
CurrentTenant.Id
);
await _apiKeyRepository.InsertAsync(apiKey);
// Return both entity and the full key (prefix_key)
// This is the ONLY time the actual key is visible
return (apiKey, $"{prefix}_{key}");
}
}
```
**Important**: The actual key is returned only once during creation. After that, only the hash is stored.
![Created Key - Copy Once](https://raw.githubusercontent.com/salihozkara/AbpApikeyManagement/refs/heads/master/docs/images/created.png)
### Using API Keys in Your Application
Once created, clients can use the API key to authenticate:
**HTTP Header (Recommended):**
```bash
curl -H "X-Api-Key: sk_prod_abc123def456..." \
https://api.example.com/api/products
```
**JavaScript:**
```javascript
const response = await fetch('https://api.example.com/api/products', {
headers: {
'X-Api-Key': 'sk_prod_abc123def456...'
}
});
```
**C# HttpClient:**
```csharp
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "sk_prod_abc123def456...");
var response = await client.GetAsync("https://api.example.com/api/products");
```
**Python:**
```python
import requests
headers = {'X-Api-Key': 'sk_prod_abc123def456...'}
response = requests.get('https://api.example.com/api/products', headers=headers)
```
### Permission Management
API keys inherit the user's permissions, but you can further restrict them:
![Permission Management](https://raw.githubusercontent.com/salihozkara/AbpApikeyManagement/refs/heads/master/docs/images/permissions.png)
This allows scenarios like:
- Read-only API key for reporting tools
- Limited scope keys for third-party integrations
- Service-specific keys with minimal permissions
```csharp
// Check if current request is authenticated via API key
if (CurrentUser.FindClaim("ApiKeyId") != null)
{
var apiKeyId = CurrentUser.FindClaim("ApiKeyId").Value;
// Additional API key specific logic
}
```
## Performance Considerations
The implementation uses several optimizations:
**1. Prefix-based indexing**: Database lookups are done by prefix (indexed column), not the full key hash.
**2. Distributed caching**: API keys are cached after first lookup, dramatically reducing database queries.
```csharp
// Cache configuration
Configure<AbpDistributedCacheOptions>(options =>
{
options.KeyPrefix = "ApiKey:";
});
```
**3. Cache invalidation**: When a key is modified or deleted, cache is automatically invalidated.
**Typical Performance:**
- Cached lookup: **< 5ms**
- Database lookup: **< 50ms**
- Cache hit rate: **~95%**
## Security Best Practices
When implementing API key authentication, follow these guidelines:
**Always use HTTPS** - Never send API keys over unencrypted connections
**Use different keys per environment** - Separate keys for dev, staging, production
**Don't log the full key** - Only log the prefix for debugging
## Getting Started
The complete source code is available on GitHub:
**Repository**: [github.com/salihozkara/AbpApikeyManagement](https://github.com/salihozkara/AbpApikeyManagement)
To integrate it into your ABP project:
1. Clone or download the repository
2. Add project references to your solution
3. Add module dependencies to your modules
4. Run EF Core migrations to create the database tables
5. Navigate to `/ApiKeyManagement` to start managing keys
```csharp
// In your Web module
[DependsOn(typeof(ApiKeyManagementWebModule))]
public class YourWebModule : AbpModule
{
// ...
}
// In your HttpApi.Host module
[DependsOn(typeof(ApiKeyManagementHttpApiModule))]
public class YourHttpApiHostModule : AbpModule
{
// ...
}
```
## Conclusion
API key authentication remains a crucial part of modern API security, especially for machine-to-machine communication. While it shouldn't replace user authentication methods like OAuth2 for user-facing applications, it's perfect for:
- Automated scripts and tools
- Service-to-service communication
- Third-party integrations
- Long-lived access without token refresh complexity
The implementation shown here demonstrates how ABP Framework's modular architecture, DDD principles, and built-in features (multi-tenancy, caching, permissions) can be leveraged to build a production-ready API key management system.
The solution is open-source and ready to be integrated into your ABP projects. Feel free to explore the code, suggest improvements, or adapt it to your specific needs.
**Resources:**
- GitHub Repository: [salihozkara/AbpApikeyManagement](https://github.com/salihozkara/AbpApikeyManagement)
- ABP Framework: [abp.io](https://abp.io)
- ABP Documentation: [docs.abp.io](https://abp.io/docs/latest)
Happy coding! 🚀

1
docs/en/Community-Articles/2025-11-15-building-an-api-key-management-system/summary.md

@ -0,0 +1 @@
Learn how to implement API key authentication in ABP Framework applications. This comprehensive guide covers what API keys are, when to use them over OAuth2/JWT, real-world use cases for mobile apps and microservices, and a complete implementation with user-based key management, SHA-256 hashing, permission delegation, and built-in UI.
Loading…
Cancel
Save