CloakId.AspNetCore
0.2.0
dotnet add package CloakId.AspNetCore --version 0.2.0
NuGet\Install-Package CloakId.AspNetCore -Version 0.2.0
<PackageReference Include="CloakId.AspNetCore" Version="0.2.0" />
<PackageVersion Include="CloakId.AspNetCore" Version="0.2.0" />
<PackageReference Include="CloakId.AspNetCore" />
paket add CloakId.AspNetCore --version 0.2.0
#r "nuget: CloakId.AspNetCore, 0.2.0"
#:package CloakId.AspNetCore@0.2.0
#addin nuget:?package=CloakId.AspNetCore&version=0.2.0
#tool nuget:?package=CloakId.AspNetCore&version=0.2.0
<div align="center">
CloakId
</div>
A .NET library that provides automatic encoding/decoding of numeric properties to obfuscated strings during JSON serialization using attributes. This helps prevent exposing internal numeric IDs in APIs while maintaining clean, readable code.
Features
- Attribute-based: Simply mark properties with
[Cloak]
to enable encoding - Automatic JSON conversion: Properties are automatically encoded to strings during serialization and decoded back during deserialization
- Pluggable encoding: Support for different encoding strategies (Sqids provided out of the box)
- Dependency injection: Full integration with Microsoft.Extensions.DependencyInjection
- Type safety: Compile-time type checking with support for all numeric types
- Nullable support: Full support for nullable numeric types
Why This Library is Useful
While CloakId is not a substitute for proper security measures like authentication, authorization, and access control, it provides valuable protection against information disclosure and business intelligence gathering.
Business Intelligence Protection
The German Tank Problem: During WWII, Allied forces estimated German tank production by analyzing captured tank serial numbers. By observing the highest serial number and using statistical analysis, they could accurately determine total production numbers and manufacturing rates. This same principle applies to modern web applications.
Real-World Scenarios
Competitor Analysis:
- A competitor signs up for your service and receives user ID
12345
- One month later, they create another account and receive user ID
15678
- Without obfuscation: They now know you gained ~3,333 users that month
- With CloakId: They see IDs like
A6das1
andxnF9Hu
- no business intelligence can be extracted
Resource Enumeration:
- Attackers often probe sequential IDs to map your system:
/api/users/1
,/api/users/2
, etc. - Without obfuscation: Reveals total user count, active vs. deleted accounts, growth patterns
- With CloakId: Each ID appears random, preventing systematic enumeration
Remember: CloakId adds a defense-in-depth layer to make unauthorized reconnaissance significantly more difficult - it does not replace fundamental security practices.
How It Works
CloakId provides a completely unintrusive solution by working transparently at the serialization boundary:
The Magic Happens at the Boundary:
- JSON Serialization: Automatic conversion from numbers → encoded strings
- JSON Deserialization: Automatic conversion from encoded strings → numbers
- Model Binding: Route parameters automatically decoded (
/users/A6das1
→id: 12345
)
This boundary-based approach provides clean separation between your client and server code:
On the Server Side:
- Your business logic works with native numeric types (
int
,long
, etc.) - No wrapper types, no special handling required
- Code remains clean and type-safe
On the Client Side:
- APIs receive and send encoded strings (
"A6das1"
,"xnF9Hu"
) - No knowledge of internal numeric values
- Consistent string-based interface
Quick Start
1. Install the NuGet packages
Install the packages using Package Manager Console:
Install-Package CloakId
Install-Package CloakId.Sqids
Alternatively, using the .NET CLI:
dotnet add package CloakId
dotnet add package CloakId.Sqids
2. Configure services
using CloakId;
using CloakId.Sqids;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddCloakId().WithSqids(options =>
{
options.MinLength = 6; // Configure minimum length
// options.Alphabet = "custom123456789abcdefghijk"; // Custom alphabet (optional)
});
var serviceProvider = services.BuildServiceProvider();
3. Use the attribute in your DTOs
public class UserDto
{
[Cloak]
public int UserId { get; set; }
[Cloak]
public long AccountId { get; set; } // Regular properties without the attribute remain unchanged
public int RegularId { get; set; }
public string Name { get; set; }
[Cloak]
public int? OptionalId { get; set; }
}
4. Configure JSON serialization
var typeInfoResolver = serviceProvider.GetRequiredService<CloakIdTypeInfoResolver>();
var jsonOptions = new JsonSerializerOptions
{
TypeInfoResolver = typeInfoResolver
};
5. Serialize/Deserialize
var user = new UserDto
{
UserId = 12345,
AccountId = 98765432109876,
RegularId = 999, // This remains as a number
Name = "John Doe",
OptionalId = 42
};
// Serialize - only [Cloak] properties become encoded strings
var json = JsonSerializer.Serialize(user, jsonOptions);
// Result: {"UserId":"A6das1","AccountId":"xnF9HulfM","RegularId":999,"Name":"John Doe","OptionalId":"JgaEBg"}
// Deserialize - strings decode back to original values
var deserializedUser = JsonSerializer.Deserialize<UserDto>(json, jsonOptions);
// deserializedUser.UserId == 12345
// deserializedUser.AccountId == 98765432109876
// deserializedUser.RegularId == 999 (unchanged)
Supported Types
The [Cloak]
attribute can be applied to the following numeric property types:
int
andint?
uint
anduint?
long
andlong?
ulong
andulong?
short
andshort?
ushort
andushort?
Direct Codec Usage
You can also use the codec directly for manual encoding/decoding:
var codec = serviceProvider.GetRequiredService<ICloakIdCodec>();
var originalValue = 12345;
var encoded = codec.Encode(originalValue, typeof(int)); // "A6das1"
var decoded = (int)codec.Decode(encoded, typeof(int)); // 12345
ASP.NET Core Model Binding
CloakId includes built-in support for ASP.NET Core model binding, allowing automatic conversion of encoded route parameters:
// Enable model binding in Program.cs
builder.Services.AddCloakId().WithSqids();
builder.Services.AddControllers().AddCloakIdModelBinding();
// Use in controllers
[HttpGet("{id}")]
public IActionResult GetUser([Cloak] int id) // Automatically converts "A6das1" → 12345
{
return Ok(new { UserId = id });
}
Routes like GET /api/users/A6das1
will automatically convert the encoded string to the numeric ID before reaching your controller method. See Model Binding Documentation for complete details.
Security Configuration
CloakId provides configurable security options for model binding:
// Configure fallback behavior for enhanced security
builder.Services.AddControllers().AddCloakIdModelBinding(options =>
{
// Disable numeric fallback for better security (default: false)
// When false: only accepts encoded strings, rejects numeric IDs
// When true: accepts both encoded strings and numeric IDs (backwards compatibility)
options.AllowNumericFallback = false;
});
Security Note: Setting AllowNumericFallback = false
provides better security by rejecting any non-encoded values, but may break existing clients that send numeric IDs. The fallback behavior could potentially expose alphabet patterns through systematic testing.
Metrics and Monitoring
CloakId includes built-in metrics using System.Diagnostics.Metrics
for monitoring security-related behavior:
Available Metrics
cloakid_model_binding_decoding_success_total
- Successful decodingscloakid_model_binding_decoding_failure_total
- Failed decodingscloakid_model_binding_numeric_fallback_total
- Security-relevant: Fallback usagecloakid_model_binding_fallback_rejection_total
- Rejected requests when fallback disabledcloakid_model_binding_decoding_duration_ms
- Decoding performance
The numeric fallback metric is particularly important for security monitoring as it can indicate potential attempts to probe the encoding alphabet through systematic testing.
Configuration Options
Fluent Configuration
// With Sqids
services.AddCloakId().WithSqids(options =>
{
options.Alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"; // Custom alphabet
options.MinLength = 8; // Minimum length of encoded strings
});
// With custom codec
services.AddCloakId().WithCustomCodec<MyCustomCodec>();
// With pre-registered Sqids
services.AddCloakId().WithRegisteredSqids();
Using Pre-registered Sqids
If you already have Sqids encoders registered in your DI container:
// First register your Sqids encoders
services.AddSingleton(new SqidsEncoder<int>(new SqidsOptions { /* your config */ }));
// ... register other encoders
// Then use the registered encoders
services.AddCloakId().WithRegisteredSqids();
Important: You cannot call both WithSqids()
and WithRegisteredSqids()
on the same builder. The second call will throw an InvalidOperationException
to prevent conflicting codec registrations.
Custom Codecs
You can implement your own encoding strategy by implementing ICloakIdCodec
:
public class MyCustomCodec : ICloakIdCodec
{
public string Encode(object value, Type valueType) { /* ... */ }
public object Decode(string encodedValue, Type targetType) { /* ... */ }
}
Using a Custom Codec
// Register custom codec by type
services.AddCloakId().WithCustomCodec<MyCustomCodec>();
// Register custom codec by instance
var myCodec = new MyCustomCodec();
services.AddCloakId().WithCustomCodec(myCodec);
// Register custom codec with factory
services.AddCloakId().WithCustomCodec(provider =>
{
var someService = provider.GetRequiredService<ISomeService>();
return new MyCustomCodec(someService);
});
Example Output
When serialized to JSON, your attributed properties will look like this:
{
"UserId": "A6das1",
"AccountId": "xnF9HulfM",
"RegularId": 999,
"Name": "John Doe",
"OptionalId": "JgaEBg"
}
Instead of exposing the raw numeric values:
{
"UserId": 12345,
"AccountId": 98765432109876,
"RegularId": 999,
"Name": "John Doe",
"OptionalId": 42
}
Notice how only the properties marked with [Cloak]
are encoded, while RegularId
remains as a number.
Performance
CloakId is designed for performance with minimal overhead. You can run comprehensive benchmarks to see the performance characteristics:
Running Benchmarks
# Run all benchmarks
./run-benchmarks.ps1
# Run only encoding/decoding benchmarks
./run-benchmarks.ps1 "*Encode*"
# Run only JSON serialization benchmarks
./run-benchmarks.ps1 "*Json*"
# Run only happy path tests
./run-benchmarks.ps1 "*HappyPath*"
# Run only error handling tests
./run-benchmarks.ps1 "*SadPath*"
# Quick validation run
./run-benchmarks.ps1 "*" --dry
Sample Results
Based on benchmarks, typical performance characteristics:
- Encoding: ~4 microseconds per int32 value
- JSON Serialization: ~40 microseconds for small models
- Memory allocation: ~21KB allocated per serialization of typical models
- Error handling: Fast exception handling for invalid data
See /benchmarks/README.md
for detailed benchmark information.
Benefits
- Security: Internal numeric IDs are not exposed in API responses
- Clean Code: Simple attribute-based approach, no wrapper types needed
- Selective: Choose exactly which properties to encode
- Type Safety: Full support for nullable types and type checking
- Performance: Efficient encoding/decoding with minimal overhead
- Flexibility: Easy to swap encoding strategies without changing business logic
License
This project is licensed under the MIT License - see the LICENSE file for details.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- CloakId (>= 0.2.0)
- Swashbuckle.AspNetCore.SwaggerGen (>= 6.5.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
0.2.0 | 209 | 8/7/2025 |
0.2.0-alpha0007 | 202 | 8/7/2025 |
0.2.0-alpha0006 | 200 | 8/7/2025 |
0.2.0-alpha0005 | 200 | 8/7/2025 |
0.2.0-alpha0004 | 200 | 8/7/2025 |
0.2.0-alpha0001 | 200 | 8/7/2025 |
0.1.0 | 204 | 8/7/2025 |
0.1.0-alpha0056 | 201 | 8/7/2025 |
0.1.0-alpha0054 | 203 | 8/7/2025 |
0.1.0-alpha0052 | 199 | 8/7/2025 |
0.1.0-alpha0051 | 201 | 8/6/2025 |
0.1.0-alpha0050 | 199 | 8/6/2025 |
0.1.0-alpha0049 | 201 | 8/6/2025 |
0.1.0-alpha0046 | 206 | 8/5/2025 |
0.1.0-alpha0045 | 205 | 8/5/2025 |