Skip to content

Repository files navigation

Security API Keys for ASP.NET Core

A flexible and lightweight API key authentication library for ASP.NET Core applications that supports multiple authentication patterns and integrates seamlessly with ASP.NET Core's authentication and authorization infrastructure.

Build Project

Coverage Status

AspNetCore.SecurityKey

Table of Contents

Overview

AspNetCore.SecurityKey provides a complete API key authentication solution for ASP.NET Core applications with support for modern development patterns and best practices.

Feature List:

  • Multiple Input Sources - API keys via headers, query parameters, or cookies
  • Flexible Authentication - Works with ASP.NET Core's built-in authentication or as standalone middleware
  • IP Address Whitelisting - Restrict API access by IP addresses and network ranges (IPv4 and IPv6)
  • Extensible Design - Custom validation and extraction logic support
  • Rich Integration - Controller attributes, middleware, and minimal API support
  • OpenAPI Support - Automatic Swagger/OpenAPI documentation generation (.NET 9+)
  • OpenTelemetry Support - Built-in activities, metrics, tags, and exception events for authentication diagnostics
  • High Performance - Minimal overhead with optional caching and timing-attack protection
  • Multiple Deployment Patterns - Attribute-based, middleware, or endpoint filters

Quick Start

  1. Install the package:

    dotnet add package AspNetCore.SecurityKey
  2. Configure your API key in appsettings.json:

    {
    "SecurityKey": "your-secret-api-key-here"
    }
  3. Register services and secure endpoints:

    builder.Services.AddSecurityKey();app.UseSecurityKey();// Secures all endpoints
  4. Call your API with the key:

    curl -H "X-API-KEY: your-secret-api-key-here" https://yourapi.com/endpoint

Installation

The library is available on nuget.org via package name AspNetCore.SecurityKey.

Package Manager Console

Install-Package AspNetCore.SecurityKey

.NET CLI

dotnet add package AspNetCore.SecurityKey

PackageReference

<PackageReferenceInclude="AspNetCore.SecurityKey" />

How to Pass API Keys

AspNetCore.SecurityKey supports multiple ways to pass API keys in requests, providing flexibility for different client scenarios:

Request Headers (Recommended)

The most common and secure approach for API-to-API communication:

GET https://api.example.com/usersAccept: application/jsonX-API-KEY: 01HSGVBSF99SK6XMJQJYF0X3WQ

Query Parameters

Useful for simple integrations or when headers cannot be easily modified:

GET https://api.example.com/users?X-API-KEY=01HSGVBSF99SK6XMJQJYF0X3WQAccept: application/json

⚠️Security Note: When using query parameters, be aware that API keys may appear in server logs, browser history, and referrer headers. Headers are generally preferred for production use.

Cookies

Ideal for browser-based applications or when API keys need persistence:

GET https://api.example.com/usersAccept: application/jsonCookie: X-API-KEY=01HSGVBSF99SK6XMJQJYF0X3WQ

Configuration

Basic Setup

Configure your API keys in appsettings.json:

{
"SecurityKey": "01HSGVBSF99SK6XMJQJYF0X3WQ"
}

Multiple API Keys

Support multiple valid API keys using semicolon separation:

{
"SecurityKey": "01HSGVBGWXWDWTFGTJSYFXXDXQ;01HSGVBSF99SK6XMJQJYF0X3WQ;01HSGVAH2M5WVQYG4YPT7FNK4K8"
}

Enhanced Configuration Format

For advanced scenarios with IP whitelisting and multiple keys, use the enhanced configuration format:

{
"SecurityKey": {
"AllowedKeys": [
"01HSGVBGWXWDWTFGTJSYFXXDXQ",
"01HSGVBSF99SK6XMJQJYF0X3WQ",
"01HSGVAH2M5WVQYG4YPT7FNK4K8"
],
"AllowedAddresses": [
"192.168.1.100",
"10.0.0.1",
"203.0.113.50",
"::1"
],
"AllowedNetworks": [
"192.168.0.0/16",
"10.0.0.0/8",
"172.16.0.0/12",
"2001:db8::/32"
]
}
}

Advanced Options

Customize key extraction and validation behavior:

builder.Services.AddSecurityKey(options =>{// Custom configuration pathoptions.ConfigurationName="Authentication:ApiKey";// Customize header name (default: "x-api-key")options.HeaderName="API-KEY";// Customize query parameter name (default: "x-api-key") options.QueryName="apikey";// Customize cookie name (default: "x-api-key")options.CookieName="app-api-key";// Case-sensitive key comparison (default: case-insensitive)options.KeyComparer=StringComparer.Ordinal;// Custom authentication scheme nameoptions.AuthenticationScheme="CustomApiKey";// Claims configurationoptions.ClaimNameType=ClaimTypes.Name;options.ClaimRoleType=ClaimTypes.Role;// Optional caching for performanceoptions.CacheTime=TimeSpan.FromMinutes(5);});

IP Address Whitelisting

AspNetCore.SecurityKey provides built-in IP address whitelisting capabilities to restrict API access based on client IP addresses. This feature supports both IPv4 and IPv6 addresses, individual IPs, and network ranges using CIDR notation.

IP Whitelisting Configuration

IP whitelisting is configured using the enhanced configuration format in appsettings.json:

{
"SecurityKey": {
"AllowedKeys": ["your-api-key-here"],
"AllowedAddresses": [
"192.168.1.100", // Specific IPv4 address"10.0.0.1", // Another IPv4 address"::1", // IPv6 localhost"2001:db8::1"// Specific IPv6 address
],
"AllowedNetworks": [
"192.168.0.0/16", // Private network range"10.0.0.0/8", // Class A private network"172.16.0.0/12", // Class B private network"2001:db8::/32"// IPv6 network range
]
}
}

How It Works

  1. No Restrictions: If neither AllowedAddresses nor AllowedNetworks are configured, all IP addresses are allowed
  2. Address Matching: Client IP is checked against the AllowedAddresses list for exact matches
  3. Network Matching: Client IP is checked against the AllowedNetworks list using CIDR notation
  4. Combined Logic: A request is allowed if the IP matches either an allowed address OR falls within an allowed network

Common Use Cases

Development Environment

Allow only local development machines:

{
"SecurityKey": {
"AllowedKeys": ["dev-key-123"],
"AllowedAddresses": [
"127.0.0.1", // IPv4 localhost"::1"// IPv6 localhost
],
"AllowedNetworks": [
"192.168.0.0/16"// Local network
]
}
}

Corporate Environment

Allow only internal corporate networks:

{
"SecurityKey": {
"AllowedKeys": ["corporate-api-key"],
"AllowedNetworks": [
"10.0.0.0/8", // Corporate internal network"172.16.0.0/12", // Secondary corporate network"203.0.113.0/24"// Public-facing servers
]
}
}

Reverse Proxy Considerations

When running behind a reverse proxy (like nginx, IIS, or cloud load balancers), ensure proper configuration to get the real client IP:

// Configure forwarded headersbuilder.Services.Configure<ForwardedHeadersOptions>(options =>{options.ForwardedHeaders=ForwardedHeaders.XForwardedFor|ForwardedHeaders.XForwardedProto;options.KnownProxies.Clear();options.KnownNetworks.Clear();});varapp=builder.Build();// Use forwarded headers before SecurityKey middlewareapp.UseForwardedHeaders();app.UseSecurityKey();

IP Whitelisting Considerations

  1. Combine with HTTPS: IP whitelisting should always be combined with HTTPS to prevent man-in-the-middle attacks
  2. Network Ranges: Be careful with broad network ranges like 0.0.0.0/0 or ::/0 as they allow all addresses
  3. Dynamic IPs: Consider that client IPs may change, especially for mobile clients or users behind NAT
  4. Proxy Headers: Validate that your reverse proxy configuration correctly forwards real client IPs
  5. Logging: Monitor failed authentication attempts to detect potential security issues

Usage Patterns

AspNetCore.SecurityKey supports multiple integration patterns to fit different application architectures and security requirements.

1. Middleware Pattern (Global Protection)

Apply API key requirement to all endpoints in your application:

varbuilder=WebApplication.CreateBuilder(args);// Register servicesbuilder.Services.AddAuthorization();builder.Services.AddSecurityKey();varapp=builder.Build();// Apply security to ALL endpointsapp.UseSecurityKey();app.UseAuthorization();// All these endpoints require valid API keysapp.MapGet("/weather",()=>WeatherService.GetForecast());app.MapGet("/users",()=>UserService.GetUsers());app.MapGet("/products",()=>ProductService.GetProducts());app.Run();

2. Attribute Pattern (Selective Protection)

Apply API key requirement to specific controllers or actions:

[ApiController][Route("[controller]")]publicclassUsersController:ControllerBase{// This action requires API key[SecurityKey][HttpGet]publicIEnumerable<User>GetUsers(){returnUserService.GetUsers();}// This action is public (no API key required)[HttpGet("public")]publicIEnumerable<User>GetPublicUsers(){returnUserService.GetPublicUsers();}}// Or apply to entire controller[SecurityKey][ApiController][Route("[controller]")]publicclassSecureController:ControllerBase{// All actions in this controller require API key[HttpGet]publicIActionResultGet()=>Ok();}

3. Endpoint Filter Pattern (Minimal APIs)

Secure specific minimal API endpoints:

varbuilder=WebApplication.CreateBuilder(args);builder.Services.AddAuthorization();builder.Services.AddSecurityKey();varapp=builder.Build();app.UseAuthorization();// Public endpoint (no API key required)app.MapGet("/health",()=>"Healthy");// Secured endpoint using filterapp.MapGet("/users",()=>UserService.GetUsers()).RequireSecurityKey();// Multiple endpoints can be groupedvarsecuredGroup=app.MapGroup("/api/secure").RequireSecurityKey();securedGroup.MapGet("/data",()=>"Secured data");securedGroup.MapPost("/action",()=>"Secured action");app.Run();

4. Authentication Scheme Pattern (Full Integration)

Integrate with ASP.NET Core's authentication system:

varbuilder=WebApplication.CreateBuilder(args);// Register authentication with SecurityKey schemebuilder.Services.AddAuthentication().AddSecurityKey();builder.Services.AddAuthorization();builder.Services.AddSecurityKey();varapp=builder.Build();app.UseAuthentication();app.UseAuthorization();// Use standard authorization attributesapp.MapGet("/users",()=>UserService.GetUsers()).RequireAuthorization();// Can also be combined with role-based authorizationapp.MapGet("/admin",()=>"Admin data").RequireAuthorization("AdminPolicy");app.Run();

Advanced Customization

Custom Security Key Validation

Implement custom validation logic by creating a class that implements ISecurityKeyValidator:

publicclassDatabaseSecurityKeyValidator:ISecurityKeyValidator{privatereadonlyIApiKeyRepository_repository;privatereadonlyILogger<DatabaseSecurityKeyValidator>_logger;publicDatabaseSecurityKeyValidator(IApiKeyRepositoryrepository,ILogger<DatabaseSecurityKeyValidator>logger){_repository=repository;_logger=logger;}publicasyncValueTask<bool>Validate(string?value,IPAddress?ipAddress=null,CancellationTokencancellationToken=default){if(string.IsNullOrEmpty(value))returnfalse;try{varapiKey=await_repository.GetApiKeyAsync(value,cancellationToken);if(apiKey==null){_logger.LogWarning("Invalid API key attempted: {Key}",value);returnfalse;}if(apiKey.IsExpired){_logger.LogWarning("Expired API key used: {Key}",value);returnfalse;}// Validate IP address if restrictions are configuredif(!IsIpAddressAllowed(ipAddress,apiKey.AllowedIpAddresses,apiKey.AllowedNetworks)){_logger.LogWarning("API key {Key} used from unauthorized IP: {IpAddress}",value,ipAddress);returnfalse;}// Update last used timestampawait_repository.UpdateLastUsedAsync(value,DateTime.UtcNow,cancellationToken);returntrue;}catch(Exceptionex){_logger.LogError(ex,"Error validating API key");returnfalse;}}publicasyncValueTask<ClaimsIdentity>Authenticate(string?value,IPAddress?ipAddress=null,CancellationTokencancellationToken=default){if(string.IsNullOrEmpty(value))returnnewClaimsIdentity();varapiKey=await_repository.GetApiKeyAsync(value,cancellationToken);if(apiKey?.User==null)returnnewClaimsIdentity();varidentity=newClaimsIdentity(SecurityKeyAuthenticationDefaults.AuthenticationScheme);identity.AddClaim(newClaim(ClaimTypes.Name,apiKey.User.Name));identity.AddClaim(newClaim(ClaimTypes.NameIdentifier,apiKey.User.Id));// Add role claimsforeach(varroleinapiKey.User.Roles){identity.AddClaim(newClaim(ClaimTypes.Role,role));}returnidentity;}privateboolIsIpAddressAllowed(IPAddress?ipAddress,string[]?allowedAddresses,string[]?allowedNetworks){// Use the built-in whitelist functionalityreturnSecurityKeyWhitelist.IsIpAllowed(ipAddress,allowedAddresses,allowedNetworks);}}// Register custom validatorbuilder.Services.AddScoped<IApiKeyRepository,ApiKeyRepository>();builder.Services.AddSecurityKey<DatabaseSecurityKeyValidator>();

Custom Security Key Extraction

Create custom extraction logic for non-standard scenarios:

publicclassCustomSecurityKeyExtractor:ISecurityKeyExtractor{privatereadonlyILogger<CustomSecurityKeyExtractor>_logger;publicCustomSecurityKeyExtractor(ILogger<CustomSecurityKeyExtractor>logger){_logger=logger;}publicstring?GetKey(HttpContext?context){if(context==null)returnnull;// Try multiple sources in priority order// 1. Authorization header with Bearer schemeif(context.Request.Headers.TryGetValue("Authorization",outvarauthHeader)){varauth=authHeader.FirstOrDefault();if(!string.IsNullOrEmpty(auth)&&auth.StartsWith("Bearer ",StringComparison.OrdinalIgnoreCase)){returnauth.Substring(7);// Remove "Bearer " prefix}}// 2. Custom headerif(context.Request.Headers.TryGetValue("X-API-TOKEN",outvartokenHeader)){returntokenHeader.FirstOrDefault();}// 3. Query parameterif(context.Request.Query.TryGetValue("access_token",outvarqueryToken)){returnqueryToken.FirstOrDefault();}// 4. Form data (for POST requests)if(context.Request.HasFormContentType&&context.Request.Form.TryGetValue("api_key",outvarformKey)){returnformKey.FirstOrDefault();}_logger.LogDebug("No API key found in request");returnnull;}}// Register both custom validator and extractorbuilder.Services.AddSecurityKey<DatabaseSecurityKeyValidator,CustomSecurityKeyExtractor>();

Rate Limiting Integration

Combine with ASP.NET Core rate limiting for enhanced security:

builder.Services.AddRateLimiter(options =>{options.AddPolicy("ApiKeyPolicy", context =>{// Extract API key for rate limitingvarapiKey=context.Request.Headers["X-API-KEY"].FirstOrDefault();returnRateLimitPartition.GetFixedWindowLimiter(partitionKey:apiKey??"anonymous",factory: _ =>newFixedWindowRateLimiterOptions{AutoReplenishment=true,PermitLimit=100,Window=TimeSpan.FromMinutes(1)});});});varapp=builder.Build();app.UseRateLimiter();app.UseSecurityKey();app.MapGet("/api/data",()=>"Data").RequireRateLimiting("ApiKeyPolicy");

OpenAPI/Swagger Integration

AspNetCore.SecurityKey provides automatic OpenAPI documentation support for .NET 9+ applications.

Basic OpenAPI Setup

varbuilder=WebApplication.CreateBuilder(args);// Register authenticationbuilder.Services.AddAuthentication().AddSecurityKey();builder.Services.AddAuthorization();builder.Services.AddSecurityKey();// Add OpenAPI with SecurityKey transformerbuilder.Services.AddOpenApi(options =>options.AddDocumentTransformer<SecurityKeyDocumentTransformer>());varapp=builder.Build();app.UseAuthentication();app.UseAuthorization();// Configure endpointsapp.MapGet("/secure-data",()=>"This endpoint requires API key").RequireAuthorization().WithOpenApi();// Expose OpenAPI documentapp.MapOpenApi();// Optional: Use Scalar for API documentationapp.MapScalarApiReference();app.Run();

Swagger/Swashbuckle Integration (Legacy)

For applications using Swashbuckle.AspNetCore:

builder.Services.AddSwaggerGen(options =>{options.AddSecurityDefinition("ApiKey",newOpenApiSecurityScheme{Type=SecuritySchemeType.ApiKey,In=ParameterLocation.Header,Name="X-API-KEY",Description="API Key needed to access the endpoints"});options.AddSecurityRequirement(newOpenApiSecurityRequirement{{newOpenApiSecurityScheme{Reference=newOpenApiReference{Type=ReferenceType.SecurityScheme,Id="ApiKey"}},Array.Empty<string>()}});});

The SecurityKeyDocumentTransformer automatically configures the OpenAPI specification to include API key authentication requirements, making it easy for developers to understand and test your API.

OpenTelemetry Support

AspNetCore.SecurityKey emits tracing and metrics using the built-in .NET diagnostics APIs, so applications can export authentication telemetry with OpenTelemetry without adding a separate instrumentation package.

Instrumentation Names

Use these stable names when configuring OpenTelemetry:

  • ActivitySource: AspNetCore.SecurityKey
  • Meter: AspNetCore.SecurityKey

Telemetry Features

  • Authentication traces for middleware, endpoint filter, MVC filter, and authentication handler flows
  • Success and failure result tags for each authentication attempt
  • Failure reason tags for invalid API keys and authentication errors
  • Hashed API key tags for tracking key usage without exposing raw keys
  • Exception events added to activities when unexpected authentication errors occur
  • Request counter for total SecurityKey authentication attempts
  • Failure counter for failed SecurityKey authentication attempts
  • Duration histogram for authentication latency in milliseconds

Metrics

MetricUnitDescription
securitykey.auth.requests{request}Number of SecurityKey authentication attempts.
securitykey.auth.failures{failure}Number of failed SecurityKey authentication attempts.
securitykey.auth.durationmsDuration of SecurityKey authentication attempts.

Tags

TagDescription
securitykey.auth.schemeAuthentication scheme name when using the authentication handler.
securitykey.auth.resultAuthentication result, such as success or failure.
securitykey.auth.failure_reasonFailure reason, such as invalid_client or authentication_error.
securitykey.auth.patternIntegration pattern, such as middleware, endpoint_filter, or mvc_filter.
securitykey.clientSHA-256 hash of the API key for tracking client usage without exposing raw keys.
securitykey.endpointResolved endpoint display name or request path.

OpenTelemetry Configuration Example

builder.Services.AddOpenTelemetry().WithTracing(tracing =>tracing.AddSource("AspNetCore.SecurityKey").AddAspNetCoreInstrumentation().AddOtlpExporter()).WithMetrics(metrics =>metrics.AddMeter("AspNetCore.SecurityKey").AddAspNetCoreInstrumentation().AddOtlpExporter());

The example assumes your application already references the OpenTelemetry packages required by the exporters and instrumentation you choose.

Best Practices

Security Considerations

  1. Use HTTPS: Always use HTTPS in production to protect API keys in transit
  2. Key Rotation: Implement regular API key rotation policies
  3. Logging: Log authentication attempts without exposing the actual keys
  4. Rate Limiting: Implement rate limiting to prevent abuse
  5. IP Whitelisting: Use IP restrictions for additional security when possible
  6. Timing Attack Protection: The library uses cryptographic operations to prevent timing attacks
  7. Telemetry Hygiene: Avoid recording raw API keys in custom telemetry tags or logs

Configuration Best Practices

  1. Environment Variables: Store sensitive keys in environment variables or secure key vaults
  2. Separate Keys: Use different API keys for different environments (dev, staging, production)
  3. Network Restrictions: Configure IP whitelisting to restrict access to known sources
  4. Monitor Usage: Implement logging and monitoring to track API key usage patterns

Performance Considerations

  1. Caching: Enable caching for authentication results when using custom validators
  2. Connection Pooling: Use connection pooling for database-backed validators
  3. Async Operations: Leverage async/await patterns for I/O operations
  4. Observability: Export SecurityKey traces and metrics with OpenTelemetry to monitor authentication latency and failures

Troubleshooting

Common Issues

Issue: API key not being extracted from requests

Solution: Check header/query parameter names match configuration:

builder.Services.AddSecurityKey(options =>{options.HeaderName="X-API-KEY";// Must match client headeroptions.QueryName="apikey";// Must match query parameter});

Issue: Authentication works but authorization fails

Solution: Ensure authentication scheme is properly configured:

// For controller attributes[Authorize(AuthenticationSchemes=SecurityKeyAuthenticationDefaults.AuthenticationScheme)]// Or set as default schemebuilder.Services.AddAuthentication(SecurityKeyAuthenticationDefaults.AuthenticationScheme).AddSecurityKey();

Issue: Custom validator not being called

Solution: Verify registration order and dependencies:

// Register dependencies firstbuilder.Services.AddScoped<IApiKeyRepository,ApiKeyRepository>();// Then register validatorbuilder.Services.AddSecurityKey<CustomValidator>();

Issue: IP whitelisting not working correctly

Solution: Check reverse proxy configuration and enable debug logging:

// Configure forwarded headersbuilder.Services.Configure<ForwardedHeadersOptions>(options =>{options.ForwardedHeaders=ForwardedHeaders.XForwardedFor|ForwardedHeaders.XForwardedProto;});varapp=builder.Build();app.UseForwardedHeaders();// Must be before UseSecurityKey()app.UseSecurityKey();

Debug Logging

Enable detailed logging to troubleshoot issues:

{
"Logging": {
"LogLevel": {
"AspNetCore.SecurityKey": "Debug",
"Microsoft.AspNetCore.Authentication": "Debug"
}
}
}

Examples Repository

For complete working examples, see the samples in this repository:

Each sample includes:

  • Complete working application
  • Configuration examples
  • HTTP test files
  • Different authentication patterns

Contributing

We welcome contributions! Please see our contributing guidelines for details.

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

Security API Key Authentication Implementation for ASP.NET Core

Topics

Resources

Contributing

Stars

12 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages