This document outlines security best practices and considerations for running UrbanX in production.
- Authentication & Authorization
- Data Protection
- Network Security
- Secrets Management
- Input Validation
- Security Headers
- Monitoring & Auditing
- Security Checklist
builder.Services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = false; // Disable in production
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = false; // Disable in production
// Use persistent key storage in production
options.KeyManagement.Enabled = true;
});The current implementation uses hardcoded test users which MUST BE REMOVED in production:
// ❌ DO NOT USE IN PRODUCTION
.AddTestUsers(new List<TestUser> { ... });
// ✅ USE THIS INSTEAD
.AddAspNetIdentity<ApplicationUser>(); // With EF Core storageConfigure password requirements:
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 12;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
options.Password.RequiredUniqueChars = 4;
});new Client
{
// Required
RequirePkce = true,
RequireClientSecret = true, // For confidential clients
// Token lifetimes (in seconds)
AccessTokenLifetime = 3600, // 1 hour
IdentityTokenLifetime = 300, // 5 minutes
RefreshTokenLifetime = 2592000, // 30 days
// Refresh token settings
RefreshTokenUsage = TokenUsage.OneTimeOnly,
RefreshTokenExpiration = TokenExpiration.Sliding,
// Security
RequireConsent = true, // Enable in production
AllowOfflineAccess = false, // Disable if not needed
}app.MapGet("/api/orders/{id}", async (Guid id, OrderDbContext db) =>
{
// Add authorization
}).RequireAuthorization("orders.read");
app.MapPost("/api/orders", async (Order order, OrderDbContext db) =>
{
// Add authorization and validation
}).RequireAuthorization("orders.write");builder.Services.AddAuthorization(options =>
{
options.AddPolicy("orders.read", policy =>
policy.RequireClaim("scope", "orders.read"));
options.AddPolicy("orders.write", policy =>
policy.RequireClaim("scope", "orders.write"));
options.AddPolicy("merchant.manage", policy =>
policy.RequireClaim("scope", "merchant.manage")
.RequireClaim("role", "merchant"));
});// Use Data Protection API for sensitive fields
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo("/keys"))
.ProtectKeysWithCertificate(certificate);
// Encrypt credit card numbers, personal data
public class CreditCard
{
[ProtectedPersonalData]
public string? Number { get; set; }
}- Always use HTTPS/TLS in production
- Configure minimum TLS version:
builder.WebHost.ConfigureKestrel(options =>
{
options.ConfigureHttpsDefaults(https =>
{
https.SslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12;
});
});// ❌ DO NOT hardcode
"Host=postgres;Database=urbanx;Username=postgres;Password=MyPassword123"
// ✅ Use environment variables
var connectionString = builder.Configuration.GetConnectionString("Database");
// ✅ Even better: Use managed identity (Azure/AWS)
builder.AddNpgsqlDbContext<OrderDbContext>("orderdb",
configureSettings: settings => settings.UseAzureManagedIdentity = true);Host=postgres;Database=urbanx;Username=postgres;Password=${PASSWORD};SslMode=Require;Trust Server Certificate=false
- Each service should have its own database user
- Grant only required permissions
- Use read-only users for read operations
-- Create service-specific user
CREATE USER order_service WITH PASSWORD 'strong_password';
GRANT CONNECT ON DATABASE urbanx_order TO order_service;
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO order_service;
-- Do NOT grant DELETE or DROP# Docker Compose
networks:
frontend: # Public-facing services
backend: # Internal services only
services:
gateway:
networks:
- frontend
- backend
order-service:
networks:
- backend # Not exposed to internetapiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: order-service-policy
namespace: urbanx
spec:
podSelector:
matchLabels:
app: order-service
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: gateway # Only gateway can access# Azure NSG
az network nsg rule create \
--name AllowHTTPS \
--nsg-name urbanx-nsg \
--priority 100 \
--source-address-prefixes Internet \
--destination-port-ranges 443 \
--access Allow
# AWS Security Group
aws ec2 authorize-security-group-ingress \
--group-id sg-xxxxx \
--protocol tcp \
--port 443 \
--cidr 0.0.0.0/0// In Gateway/Program.cs
builder.Services.AddRateLimiter(options =>
{
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
{
return RateLimitPartition.GetFixedWindowLimiter(
partitionKey: context.User.Identity?.Name ?? context.Request.Headers.Host.ToString(),
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
});
});
});
app.UseRateLimiter();// Never do this
var password = "MyPassword123";
var apiKey = "sk_live_abc123def456";
builder.Services.AddDbContext(options =>
options.UseNpgsql("Host=localhost;Password=MyPassword"));builder.Configuration.AddAzureKeyVault(
new Uri($"https://{keyVaultName}.vault.azure.net/"),
new DefaultAzureCredential());builder.Configuration.AddSecretsManager(
configurator: options =>
{
options.SecretFilter = entry => entry.Name.StartsWith("urbanx/");
});# Use secrets, not plain env vars
kubectl create secret generic db-secret \
--from-literal=password='$(openssl rand -base64 32)'- Rotate database passwords every 90 days
- Rotate API keys every 180 days
- Automate rotation with scripts or managed services
- Update all services after rotation
The shared validation library is available at src/Shared/UrbanX.Shared.Security/RequestValidation.cs
using UrbanX.Shared.Security;
app.MapPost("/api/cart/{customerId}/items", async (
Guid customerId,
CartItem item,
OrderDbContext db) =>
{
// Validate GUID
RequestValidation.ValidateGuid(customerId, nameof(customerId));
// Validate item properties
RequestValidation.ValidateGuid(item.ProductId, nameof(item.ProductId));
RequestValidation.ValidatePositive(item.Quantity, nameof(item.Quantity));
RequestValidation.ValidatePositive(item.Price, nameof(item.Price));
// Business logic
// ...
});- ✅ Use parameterized queries (EF Core does this automatically)
- ✅ Use ORM (Entity Framework Core)
- ❌ Never concatenate user input into SQL
// ✅ Safe: Parameterized
var order = await db.Orders.FirstOrDefaultAsync(o => o.Id == orderId);
// ❌ Dangerous: SQL Injection vulnerability
var query = $"SELECT * FROM Orders WHERE Id = '{orderId}'";- ✅ Use Razor/Blazor encoding (automatic)
- ✅ Validate and sanitize all HTML input
- ✅ Set Content-Security-Policy header
// Enable anti-forgery for state-changing operations
builder.Services.AddAntiforgery();
app.MapPost("/api/orders", async (Order order, OrderDbContext db) =>
{
// ...
}).RequireAntiforgery(); // Add CSRF protectionSecurity headers are automatically added by UseProductionDefaults() in ServiceDefaults:
app.Use(async (context, next) =>
{
context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
context.Response.Headers.Append("X-Frame-Options", "DENY");
context.Response.Headers.Append("X-XSS-Protection", "1; mode=block");
context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
// Content Security Policy
context.Response.Headers.Append("Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");
await next();
});// Strict Transport Security (HSTS)
app.UseHsts(); // Forces HTTPS for 1 year
// Permissions Policy
context.Response.Headers.Append("Permissions-Policy",
"geolocation=(), microphone=(), camera=()");// Log authentication events
builder.Services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true; // Enable in production for auditing
});
// Log authorization failures
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});Log security-relevant events:
- Authentication attempts (success/failure)
- Authorization failures
- Sensitive data access (orders, payments, user data)
- Configuration changes
- Admin operations
app.MapGet("/api/orders/{id}", async (
Guid id,
OrderDbContext db,
ILogger<Program> logger,
ClaimsPrincipal user) =>
{
logger.LogInformation(
"User {UserId} accessed order {OrderId}",
user.FindFirst("sub")?.Value,
id);
// Fetch order
});Configure alerts for:
- Multiple failed authentication attempts
- Authorization failures (potential attack)
- Unusual API usage patterns
- Database access anomalies
- Configuration changes
- Remove hardcoded test users from Identity Service
- Configure strong password policy
- Enable HTTPS/TLS for all services
- Add authorization to all API endpoints
- Configure proper token lifetimes
- Enable audit logging for auth events
- Move all secrets to secure vault (Azure Key Vault / AWS Secrets Manager)
- Remove secrets from appsettings.json
- Verify .env files are in .gitignore
- Configure secret rotation policy
- Use managed identities where possible
- Add validation to all POST/PUT endpoints
- Validate GUIDs, required fields, ranges
- Implement anti-forgery tokens
- Add rate limiting
- Sanitize HTML inputs
- Configure firewall rules
- Implement network segmentation
- Use private networks for internal services
- Enable database SSL/TLS
- Configure CORS properly (not localhost)
- Enable encryption at rest for databases
- Use TLS 1.2+ for all connections
- Implement data classification
- Configure automatic backups
- Test backup restoration
- Configure centralized logging
- Set up security alerts
- Enable audit trails
- Configure log retention policy
- Test alerting mechanisms
- Scan images for vulnerabilities (Trivy, Snyk)
- Run containers as non-root user
- Use minimal base images
- Keep base images updated
- Implement resource limits
- Use strong database passwords (16+ characters)
- Create service-specific database users
- Grant minimum required permissions
- Enable database audit logging
- Restrict database network access
- Penetration testing performed
- Security scan completed (no critical issues)
- Vulnerability scan completed
- SSL/TLS configuration verified (A+ rating)
- Security headers verified
- Authentication flows tested
- Authorization policies tested
- Rate limiting tested
- Backup and restore tested
- Incident response plan documented
For security issues, please contact:
- Security Team: security@yourdomain.com
- On-Call: +1-XXX-XXX-XXXX
- Monitor security alerts
- Review authentication logs for anomalies
- Review access logs
- Check for CVE announcements for dependencies
- Update container images if needed
- Review and rotate API keys
- Audit user permissions
- Review firewall rules
- Check SSL certificate expiration
- Rotate database passwords
- Conduct security training
- Review and update security policies
- Penetration testing
- Comprehensive security audit
- Disaster recovery drill
- Update security documentation
- Review compliance requirements