Zero-config Basic Authentication for ASP.NET Core. Inline credentials, per-endpoint auth, URL-based auth, rate limiting, IP whitelisting, and audit logging out of the box.
- 🔐 Simple Setup - One-liner configuration with inline credentials
- 🎯 Per-Endpoint Auth - Different credentials for different endpoints
- 🌐 URL-Based Auth - Protect URLs by pattern with middleware
- 👥 Multi-User Support - Configure multiple users with roles and claims
- ⏱️ Rate Limiting - Protect against brute force attacks
- 🌍 IP Whitelisting - Allow/block specific IP addresses or CIDR ranges
- 📝 Audit Logging - Track authentication attempts
- 🔒 Password Hashing - Support for SHA256, SHA512, and BCrypt
- 📅 Access Schedules - Time-based access restrictions
- 🎪 Event Hooks - Custom logic for authentication lifecycle
By using this project or its source code, for any purpose and in any shape or form, you grant your implicit agreement to all of the following statements:
- You unequivocally condemn Russia and its military aggression against Ukraine
- You recognize that Russia is an occupant that unlawfully invaded a sovereign state
- You agree that Russia is a terrorist state
- You fully support Ukraine's territorial integrity, including its claims over temporarily occupied territories
- You reject false narratives perpetuated by Russian state propaganda
To learn more about the war and how you can help, click here. Glory to Ukraine! 🇺🇦
dotnet add package BasicAuthGuardvar builder = WebApplication.CreateBuilder(args);
// Add basic auth with inline credentials
builder.Services.AddBasicAuthGuard("admin", "password");
var app = builder.Build();
// Protect an endpoint
app.MapGet("/api/secret", () => "Secret data")
.RequireBasicAuth();
app.Run();app.MapGet("/api/users", () => GetUsers())
.RequireBasicAuth("user", "userpass");
app.MapGet("/api/admin", () => GetAdminData())
.RequireBasicAuth("admin", "adminpass", realm: "Admin Area");Protect endpoints by URL pattern without modifying endpoint definitions:
var app = builder.Build();
// Simple: protect a single URL
app.AddBasicAuthGuard("/health", "healthuser", "healthpass");
// With realm
app.AddBasicAuthGuard("/api/admin/*", "admin", "adminpass", "Admin Area");
// Multiple patterns at once
app.UseBasicAuthGuard(auth =>
{
auth.AddPattern("/metrics", "metrics", "metricspass");
auth.AddPattern("/api/internal/**", options =>
{
options.Username = "internal";
options.Password = "internalpass";
options.Realm = "Internal API";
options.Roles.Add("InternalUser");
});
});
// Your endpoints (no auth attributes needed)
app.MapGet("/health", () => Results.Ok(new { status = "healthy" }));
app.MapGet("/metrics", () => GetMetrics());URL Pattern Wildcards:
*- Matches any single path segment (e.g.,/api/*matches/api/usersbut not/api/users/1)**- Matches any path including nested segments (e.g.,/api/**matches/api/users/1/orders)
- Service Registration
- Endpoint Protection
- URL-Based Protection
- Multi-User Configuration
- Password Hashing
- Rate Limiting
- IP Whitelisting
- Audit Logging
- Access Schedules
- Custom Validation
- Events
- Configuration Options Reference
Register basic authentication in the DI container.
// Inline credentials
builder.Services.AddBasicAuthGuard("username", "password");
// With realm
builder.Services.AddBasicAuthGuard("username", "password", "MyRealm");
// With configuration delegate
builder.Services.AddBasicAuthGuard(options =>
{
options.Username = "admin";
options.Password = "password";
options.Realm = "Protected Area";
});
// With custom scheme name
builder.Services.AddBasicAuthGuard("CustomScheme", options =>
{
options.Username = "admin";
options.Password = "password";
});
// From configuration section
builder.Services.AddBasicAuthGuard(builder.Configuration.GetSection("BasicAuth"));Add an authorization policy for basic auth.
builder.Services.AddBasicAuthGuard("admin", "password");
builder.Services.AddBasicAuthGuardPolicy(); // Default policy name
// Custom policy name
builder.Services.AddBasicAuthGuardPolicy("MyBasicAuthPolicy", "CustomScheme");Protect individual endpoints with basic authentication.
// Use global credentials (from AddBasicAuthGuard)
app.MapGet("/api/data", () => "data")
.RequireBasicAuth();
// Per-endpoint credentials
app.MapGet("/api/users", () => GetUsers())
.RequireBasicAuth("user", "userpass");
// With realm
app.MapGet("/api/admin", () => GetAdminData())
.RequireBasicAuth("admin", "adminpass", realm: "Admin Area");
// With full configuration
app.MapGet("/api/special", () => "special")
.RequireBasicAuth(options =>
{
options.Username = "special";
options.Password = "specialpass";
options.Realm = "Special Area";
options.Roles.Add("SpecialUser");
options.Claims.Add(new Claim("department", "IT"));
});Options for per-endpoint authentication:
| Property | Type | Description |
|---|---|---|
Username |
string? |
Required username |
Password |
string? |
Required password |
Realm |
string? |
Realm for WWW-Authenticate header |
ValidateCredentialsAsync |
Func<string, string, HttpContext, Task<bool>>? |
Custom validation delegate |
Claims |
IList<Claim> |
Additional claims to add |
Roles |
IList<string> |
Roles to assign |
Protect URLs using middleware without modifying endpoint definitions.
// Simple credentials
app.AddBasicAuthGuard("/health", "user", "pass");
// With realm
app.AddBasicAuthGuard("/api/admin/*", "admin", "adminpass", "Admin Area");
// With full configuration
app.AddBasicAuthGuard("/api/internal", options =>
{
options.Username = "internal";
options.Password = "internalpass";
options.Realm = "Internal API";
options.Roles.Add("InternalUser");
options.CaseInsensitiveUrlMatching = true;
});Configure multiple URL patterns at once:
app.UseBasicAuthGuard(auth =>
{
// Simple pattern
auth.AddPattern("/health", "health", "healthpass");
// Wildcard patterns
auth.AddPattern("/api/v1/*", "apiv1", "apiv1pass");
auth.AddPattern("/api/v2/**", "apiv2", "apiv2pass");
// With full options
auth.AddPattern("/admin/**", options =>
{
options.Username = "admin";
options.Password = "adminpass";
options.Realm = "Administration";
options.Roles.Add("Admin");
options.ValidateCredentialsAsync = async (user, pass, ctx) =>
{
// Custom validation logic
return user == "admin" && pass == "adminpass";
};
});
});| Property | Type | Default | Description |
|---|---|---|---|
UrlPattern |
string |
"" |
URL pattern to match |
Username |
string? |
null |
Required username |
Password |
string? |
null |
Required password |
Realm |
string? |
null |
Realm for WWW-Authenticate header |
ValidateCredentialsAsync |
Func<...>? |
null |
Custom validation delegate |
Claims |
IList<Claim> |
[] |
Additional claims |
Roles |
IList<string> |
[] |
Roles to assign |
CaseInsensitiveUrlMatching |
bool |
true |
Case-insensitive URL matching |
Configure multiple users with different credentials, roles, and permissions.
builder.Services.AddBasicAuthGuard(options =>
{
// Add users with fluent API
options.AddUser("admin", "adminpass",
roles: ["Admin", "User"],
claims: [new Claim("department", "IT")]);
options.AddUser("user", "userpass",
roles: ["User"]);
options.AddUser("readonly", "readonlypass",
roles: ["Reader"]);
});| Property | Type | Description |
|---|---|---|
Username |
string |
Username for authentication |
Password |
string? |
Plain text password |
PasswordHash |
string? |
Hashed password |
Roles |
IReadOnlyList<string> |
Assigned roles |
Claims |
IReadOnlyList<Claim> |
Additional claims |
IsEnabled |
bool |
Whether account is active |
Schedule |
AccessSchedule? |
Time-based restrictions |
Store passwords securely using hashing algorithms.
| Algorithm | Enum Value | Description |
|---|---|---|
| None | PasswordHashAlgorithm.None |
Plain text (not recommended) |
| SHA256 | PasswordHashAlgorithm.SHA256 |
SHA-256 hash |
| SHA512 | PasswordHashAlgorithm.SHA512 |
SHA-512 hash |
| BCrypt | PasswordHashAlgorithm.BCrypt |
BCrypt (PBKDF2-based, recommended) |
builder.Services.AddBasicAuthGuard(options =>
{
options.HashAlgorithm = PasswordHashAlgorithm.BCrypt;
// Add user with pre-hashed password
options.AddUserWithHash(
username: "admin",
passwordHash: "hashed_password_here",
algorithm: PasswordHashAlgorithm.BCrypt,
roles: ["Admin"]);
});You can also inject IPasswordHasher to hash passwords programmatically:
public class MyService
{
private readonly IPasswordHasher _hasher;
public MyService(IPasswordHasher hasher) => _hasher = hasher;
public string HashPassword(string password)
{
return _hasher.Hash(password, PasswordHashAlgorithm.BCrypt);
}
public bool VerifyPassword(string password, string hash)
{
return _hasher.Verify(password, hash, PasswordHashAlgorithm.BCrypt);
}
}Protect against brute force attacks by limiting failed authentication attempts.
builder.Services.AddBasicAuthGuard(options =>
{
options.Username = "admin";
options.Password = "password";
options.WithRateLimiting(rate =>
{
rate.MaxFailedAttempts = 5; // Lock after 5 failures
rate.LockoutDuration = TimeSpan.FromMinutes(15); // Lock for 15 minutes
rate.AttemptWindow = TimeSpan.FromMinutes(5); // Count failures within 5 min window
rate.PerIp = true; // Track per IP address
rate.IncludeUsername = true; // Include username in lockout key
rate.LockoutMessage = "Too many failed attempts. Please try again later.";
});
});| Property | Type | Default | Description |
|---|---|---|---|
MaxFailedAttempts |
int |
5 |
Max failures before lockout |
LockoutDuration |
TimeSpan |
15 min |
Duration of lockout |
AttemptWindow |
TimeSpan |
5 min |
Window for counting failures |
PerIp |
bool |
true |
Track per IP address |
IncludeUsername |
bool |
true |
Include username in lockout key |
LockoutMessage |
string? |
null |
Custom lockout message |
Control access based on client IP addresses.
builder.Services.AddBasicAuthGuard(options =>
{
options.Username = "admin";
options.Password = "password";
options.WithIpWhitelist(ip =>
{
// Allow specific IPs or CIDR ranges
ip.AllowedRanges = ["192.168.1.0/24", "10.0.0.1", "::1"];
// Block specific IPs
ip.BlockedRanges = ["192.168.1.100"];
// Skip auth entirely for whitelisted IPs
ip.BypassAuthForAllowedIps = true;
// Reject if not in whitelist (when AllowedRanges is set)
ip.RejectIfNotWhitelisted = true;
// Custom message for blocked IPs
ip.BlockedMessage = "Access denied from your IP address.";
});
});| Property | Type | Default | Description |
|---|---|---|---|
AllowedRanges |
IList<string> |
[] |
Allowed IPs/CIDR ranges |
BlockedRanges |
IList<string> |
[] |
Blocked IPs/CIDR ranges |
BypassAuthForAllowedIps |
bool |
false |
Skip auth for allowed IPs |
RejectIfNotWhitelisted |
bool |
true |
Reject if not whitelisted |
BlockedMessage |
string? |
null |
Custom blocked message |
Track all authentication attempts for security monitoring.
builder.Services.AddBasicAuthGuard(options =>
{
options.Username = "admin";
options.Password = "password";
options.WithAuditLog(audit =>
{
audit.Enabled = true;
audit.SuccessLogLevel = LogLevel.Information;
audit.FailureLogLevel = LogLevel.Warning;
audit.IncludeIpAddress = true;
audit.IncludeUserAgent = true;
audit.IncludeRequestPath = true;
audit.IncludeUsernameOnFailure = true;
// Custom log message templates
audit.SuccessMessageTemplate =
"Auth success: {Username} from {IpAddress} accessing {Path}";
audit.FailureMessageTemplate =
"Auth failed: {Username} from {IpAddress} - {Reason}";
});
});| Property | Type | Default | Description |
|---|---|---|---|
Enabled |
bool |
true |
Enable audit logging |
SuccessLogLevel |
LogLevel |
Information |
Log level for success |
FailureLogLevel |
LogLevel |
Warning |
Log level for failures |
IncludeIpAddress |
bool |
true |
Include client IP |
IncludeUserAgent |
bool |
false |
Include User-Agent header |
IncludeRequestPath |
bool |
true |
Include request path |
IncludeUsernameOnFailure |
bool |
true |
Include username on failures |
SuccessMessageTemplate |
string? |
null |
Custom success message |
FailureMessageTemplate |
string? |
null |
Custom failure message |
Available Placeholders: {Username}, {IpAddress}, {UserAgent}, {Path}, {Scheme}, {Reason}
Restrict user access to specific days and hours.
builder.Services.AddBasicAuthGuard(options =>
{
// Add user with schedule restrictions
options.AddUserWithSchedule(
username: "contractor",
password: "contractorpass",
schedule: new AccessSchedule
{
AllowedDays = [DayOfWeek.Monday, DayOfWeek.Tuesday,
DayOfWeek.Wednesday, DayOfWeek.Thursday,
DayOfWeek.Friday],
AllowedFromHour = 9, // 9 AM
AllowedToHour = 17, // 5 PM
TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time")
},
roles: ["Contractor"]);
});| Property | Type | Default | Description |
|---|---|---|---|
AllowedDays |
IReadOnlyList<DayOfWeek> |
[] |
Allowed days (empty = all) |
AllowedFromHour |
int? |
null |
Start hour (0-23) |
AllowedToHour |
int? |
null |
End hour (0-23) |
TimeZone |
TimeZoneInfo |
UTC |
Timezone for evaluation |
Implement custom credential validation logic.
builder.Services.AddBasicAuthGuard(options =>
{
options.ValidateCredentialsAsync = async (username, password, context) =>
{
// Custom validation logic (e.g., database lookup)
var user = await _userService.ValidateAsync(username, password);
return user != null;
};
});builder.Services.AddBasicAuthGuard(options =>
{
options.Events.OnValidateCredentials = async context =>
{
// Access username and password
var username = context.Username;
var password = context.Password;
// Validate against your user store
var isValid = await ValidateUserAsync(username, password);
if (isValid)
{
// Add custom claims
context.ValidationSucceeded([
new Claim("custom-claim", "value"),
new Claim(ClaimTypes.Role, "CustomRole")
]);
}
else
{
context.ValidationFailed("Invalid credentials");
}
};
});builder.Services.AddBasicAuthGuard(options =>
{
options.Username = "admin";
options.Password = "password";
options.GetAdditionalClaimsAsync = async (username, context) =>
{
// Fetch additional claims from database
var userClaims = await _claimService.GetClaimsAsync(username);
return userClaims;
};
});Hook into the authentication lifecycle for custom behavior.
builder.Services.AddBasicAuthGuard(options =>
{
options.Username = "admin";
options.Password = "password";
options.Events.OnValidateCredentials = async context =>
{
// Custom credential validation
if (context.Username == "admin" && context.Password == "admin")
{
context.ValidationSucceeded();
}
else
{
context.ValidationFailed("Invalid credentials");
}
};
options.Events.OnAuthenticationSucceeded = async context =>
{
// Log successful authentication
Console.WriteLine($"User {context.Username} authenticated successfully");
};
options.Events.OnAuthenticationFailed = async context =>
{
// Log failed authentication
Console.WriteLine($"Authentication failed for {context.Username}: {context.FailureReason}");
};
options.Events.OnChallenge = async context =>
{
// Customize challenge response
context.Response.Headers.Append("X-Custom-Header", "value");
};
options.Events.OnForbidden = async context =>
{
// Customize forbidden response
context.Handled = true;
context.Response.StatusCode = 403;
await context.Response.WriteAsync("Access denied");
};
});| Event | Description |
|---|---|
OnValidateCredentials |
Validate credentials with custom logic |
OnAuthenticationSucceeded |
Called after successful authentication |
OnAuthenticationFailed |
Called after failed authentication |
OnChallenge |
Customize 401 challenge response |
OnForbidden |
Customize 403 forbidden response |
| Property | Type | Default | Description |
|---|---|---|---|
Realm |
string |
"Protected" |
Realm for WWW-Authenticate |
Username |
string? |
null |
Single user username |
Password |
string? |
null |
Single user password (plain) |
PasswordHash |
string? |
null |
Single user password (hashed) |
HashAlgorithm |
PasswordHashAlgorithm |
None |
Hash algorithm to use |
Users |
IList<BasicAuthenticationUser> |
[] |
Configured users |
SuppressWwwAuthenticateHeader |
bool |
false |
Suppress WWW-Authenticate header |
IgnoreAuthenticationIfAllowAnonymous |
bool |
true |
Skip auth for [AllowAnonymous] |
RateLimiting |
RateLimitOptions? |
null |
Rate limiting config |
IpWhitelist |
IpWhitelistOptions? |
null |
IP whitelist config |
AuditLog |
AuditLogOptions? |
null |
Audit logging config |
ValidateCredentialsAsync |
Func<...>? |
null |
Custom validation delegate |
GetAdditionalClaimsAsync |
Func<...>? |
null |
Additional claims provider |
Events |
BasicAuthenticationEvents |
new() |
Event handlers |
public class BasicAuthenticationDefaults
{
public const string AuthenticationScheme = "BasicAuthentication";
public const string Realm = "Protected";
public const string PolicyName = "BasicAuthenticationPolicy";
}using AspNetCore.BasicAuthentication.Extensions;
using System.Security.Claims;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
// Configure comprehensive basic authentication
builder.Services.AddBasicAuthGuard(options =>
{
options.Realm = "My API";
options.HashAlgorithm = PasswordHashAlgorithm.BCrypt;
// Add multiple users
options.AddUser("admin", "adminpass", roles: ["Admin", "User"]);
options.AddUser("user", "userpass", roles: ["User"]);
options.AddUserWithSchedule("contractor", "contractorpass",
new AccessSchedule
{
AllowedDays = [DayOfWeek.Monday, DayOfWeek.Tuesday,
DayOfWeek.Wednesday, DayOfWeek.Thursday,
DayOfWeek.Friday],
AllowedFromHour = 9,
AllowedToHour = 17
},
roles: ["Contractor"]);
// Rate limiting
options.WithRateLimiting(rate =>
{
rate.MaxFailedAttempts = 5;
rate.LockoutDuration = TimeSpan.FromMinutes(15);
});
// IP restrictions
options.WithIpWhitelist(ip =>
{
ip.AllowedRanges = ["10.0.0.0/8", "192.168.0.0/16"];
});
// Audit logging
options.WithAuditLog(audit =>
{
audit.Enabled = true;
audit.IncludeIpAddress = true;
audit.IncludeUserAgent = true;
});
});
var app = builder.Build();
// URL-based authentication
app.AddBasicAuthGuard("/health", "monitor", "monitorpass");
app.UseBasicAuthGuard(auth =>
{
auth.AddPattern("/metrics", "metrics", "metricspass");
auth.AddPattern("/api/internal/**", "internal", "internalpass");
});
// Public endpoint
app.MapGet("/", () => "Welcome!");
// Protected endpoints with per-endpoint auth
app.MapGet("/api/users", () => new[] { "user1", "user2" })
.RequireBasicAuth();
app.MapGet("/api/admin", () => "Admin data")
.RequireBasicAuth("admin", "adminpass");
// Health check (protected by URL-based auth)
app.MapGet("/health", () => Results.Ok(new { status = "healthy" }));
app.Run();MIT License - see LICENSE for details.