Skip to main content

Overview

Deploying AuthService to production requires several critical security and infrastructure changes. This guide provides a comprehensive checklist to ensure your deployment is secure, scalable, and reliable.
DO NOT deploy to production without completing this checklist. The default configuration is designed for development only and is not secure for production use.

Pre-Deployment Checklist

1. Secure JWT Secret Key

1

Generate a strong secret key

The default secret key must be replaced with a cryptographically secure random value.Current development config (appsettings.json:6):
"SecretKey": "CAMBIA-ESTO-EN-PRODUCCION-usa-un-valor-largo-y-aleatorio-min32chars"
Generate a secure key:
openssl rand -base64 64
If an attacker obtains your secret key, they can forge valid JWT tokens for any user. This is your most critical security setting.
2

Store in secure vault

Never store the production secret key in appsettings.json. Use:
# Store secret in Azure Key Vault
az keyvault secret set --vault-name your-vault \
  --name JwtSecretKey \
  --value "your-generated-secret-key"
Configure in Program.cs:
builder.Configuration.AddAzureKeyVault(
    new Uri($"https://{vaultName}.vault.azure.net/"),
    new DefaultAzureCredential());
3

Update production config

In appsettings.Production.json, reference the secret from environment:
{
  "Jwt": {
    "SecretKey": "${JWT_SECRET_KEY}"
  }
}
Or remove the key entirely and set via environment variable.

2. Switch to Production Database

The default SQLite database is not suitable for production.
1

Choose a production database

Current development config (appsettings.json:2-3):
"ConnectionStrings": {
  "DefaultConnection": "Data Source=authservice.db"
}
SQLite is single-file and not designed for concurrent access or high availability.
2

Install database provider

dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
Update Program.cs:13-15:
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql(
        builder.Configuration.GetConnectionString("DefaultConnection")));
Connection string:
"ConnectionStrings": {
  "DefaultConnection": "Host=db.example.com;Database=authservice;Username=authuser;Password=${DB_PASSWORD}"
}
3

Run migrations

Remove the development auto-migration code from Program.cs:85-91:
// REMOVE THIS IN PRODUCTION:
if (app.Environment.IsDevelopment())
{
    using var scope = app.Services.CreateScope();
    var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    db.Database.EnsureCreated();
}
Run migrations manually:
dotnet ef migrations add InitialCreate
dotnet ef database update
EnsureCreated() does not use migrations and will fail if the database already exists. Always use migrations in production.
4

Secure connection string

Store connection string in environment variables or secure vault:
export ConnectionStrings__DefaultConnection="Server=...;Password=..."

3. Enable HTTPS

1

Obtain SSL/TLS certificate

Options:
  • Let’s Encrypt (free, automated)
  • Commercial certificate authority
  • Cloud provider certificates (AWS ACM, Azure App Service Certificates)
2

Configure HTTPS in Kestrel

Add to appsettings.Production.json:
{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://*:443",
        "Certificate": {
          "Path": "/etc/ssl/certs/authservice.pfx",
          "Password": "${CERT_PASSWORD}"
        }
      }
    }
  }
}
3

Enforce HTTPS redirection

The service already includes HTTPS redirection in Program.cs:102:
app.UseHttpsRedirection();
If behind a reverse proxy (nginx, load balancer), configure forwarded headers:
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
4

Update allowed hosts

Restrict allowed hosts in appsettings.Production.json:
{
  "AllowedHosts": "yourdomain.com,api.yourdomain.com"
}

4. Implement Rate Limiting

Protect against brute-force attacks and API abuse.
1

Add rate limiting middleware

Install package:
dotnet add package AspNetCoreRateLimit
Or use built-in .NET 7+ rate limiting:
dotnet add package Microsoft.AspNetCore.RateLimiting
2

Configure rate limits

Add to Program.cs after var builder = WebApplication.CreateBuilder(args);:
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;

// Add rate limiting
builder.Services.AddRateLimiter(options =>
{
    // Strict limit for authentication endpoints
    options.AddFixedWindowLimiter("auth", opt =>
    {
        opt.Window = TimeSpan.FromMinutes(1);
        opt.PermitLimit = 5;
        opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        opt.QueueLimit = 0;
    });
    
    // General API limit
    options.AddFixedWindowLimiter("api", opt =>
    {
        opt.Window = TimeSpan.FromMinutes(1);
        opt.PermitLimit = 100;
    });
});
Apply to endpoints in AuthController.cs:
[HttpPost("login")]
[EnableRateLimiting("auth")]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
    // ...
}
Enable in pipeline (Program.cs after app.UseHttpsRedirection()):
app.UseRateLimiter();
3

Configure per-IP limits

For more granular control:
options.AddSlidingWindowLimiter("sliding", opt =>
{
    opt.Window = TimeSpan.FromMinutes(1);
    opt.PermitLimit = 10;
    opt.SegmentsPerWindow = 4;
});
The service already implements account lockout after 5 failed login attempts (AuthService.cs:26-27). Rate limiting provides an additional layer of protection at the network level.

5. Configure Logging and Monitoring

1

Reduce log verbosity

Update appsettings.Production.json:
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "Microsoft.AspNetCore": "Error",
      "Microsoft.EntityFrameworkCore": "Error"
    }
  }
}
2

Add structured logging

Install Serilog:
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File
Configure in Program.cs:
using Serilog;

Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .WriteTo.File("logs/authservice-.log", rollingInterval: RollingInterval.Day)
    .CreateLogger();

builder.Host.UseSerilog();
3

Integrate monitoring

Add Application Performance Monitoring (APM):
  • Application Insights (Azure)
  • CloudWatch (AWS)
  • Datadog
  • New Relic
  • Elastic APM
Monitor key metrics:
  • Request rate and latency
  • Failed login attempts
  • Token refresh rate
  • Database connection pool
  • Error rates

6. Implement Key Rotation

Regularly rotate JWT secret keys to limit impact of compromise.
1

Support multiple signing keys

Modify token validation to accept multiple keys:
var jwtSection = builder.Configuration.GetSection("Jwt");
var currentKey = jwtSection["SecretKey"];
var previousKey = jwtSection["PreviousSecretKey"]; // For rotation

var signingKeys = new List<SecurityKey>
{
    new SymmetricSecurityKey(Encoding.UTF8.GetBytes(currentKey))
};

if (!string.IsNullOrEmpty(previousKey))
{
    signingKeys.Add(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(previousKey)));
}

builder.Services.AddAuthentication(/* ... */)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKeys = signingKeys, // Accept multiple keys
            // ... other settings
        };
    });
2

Update TokenService to use current key

Ensure TokenService.cs:29-30 always uses the current key for signing new tokens.
3

Rotation procedure

When rotating:
  1. Generate new key
  2. Set as SecretKey, move old key to PreviousSecretKey
  3. Deploy configuration
  4. Wait for old tokens to expire (AccessTokenExpiryMinutes)
  5. Remove PreviousSecretKey
Automate rotation with:
  • Scheduled task (monthly/quarterly)
  • Infrastructure as Code (Terraform, CloudFormation)
  • Key management service rotation

7. Disable Development Features

1

Remove Swagger in production

Swagger is already scoped to development in Program.cs:86-98:
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(/* ... */);
}
Ensure ASPNETCORE_ENVIRONMENT=Production is set.
2

Disable detailed errors

Add to Program.cs:
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/error");
    app.UseHsts();
}
The existing ExceptionHandlingMiddleware already provides safe error responses.

8. Security Headers

Add security headers to protect against common attacks.
1

Add security headers middleware

Add to Program.cs after var app = builder.Build();:
app.Use(async (context, next) =>
{
    context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
    context.Response.Headers.Add("X-Frame-Options", "DENY");
    context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
    context.Response.Headers.Add("Referrer-Policy", "no-referrer");
    context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'");
    await next();
});
2

Configure CORS properly

Add CORS if your API is consumed by web clients:
builder.Services.AddCors(options =>
{
    options.AddPolicy("Production", policy =>
    {
        policy.WithOrigins("https://yourdomain.com")
              .AllowAnyMethod()
              .AllowAnyHeader();
    });
});

// In pipeline:
app.UseCors("Production");

Deployment Architecture

┌─────────────┐
│ Load        │
│ Balancer    │ ← HTTPS (443)
└──────┬──────┘

       ├─────────────┬─────────────┐
       │             │             │
  ┌────▼────┐   ┌───▼─────┐  ┌───▼─────┐
  │ Auth    │   │ Auth    │  │ Auth    │
  │ Service │   │ Service │  │ Service │
  │ (Instance)  │ (Instance)  │ (Instance)
  └────┬────┘   └───┬─────┘  └───┬─────┘
       │            │             │
       └────────────┴─────────────┘

            ┌───────▼────────┐
            │   Database     │
            │  (PostgreSQL/  │
            │   SQL Server)  │
            └────────────────┘
For high availability:
  • Run at least 2 instances behind a load balancer
  • Use a managed database service with automatic backups
  • Implement health checks (see below)
  • Use container orchestration (Kubernetes, ECS)

Health Checks

Add health check endpoints:
builder.Services.AddHealthChecks()
    .AddDbContextCheck<AppDbContext>();

app.MapHealthChecks("/health");

Deployment Checklist

Use this checklist before every production deployment.
  • JWT SecretKey changed and stored securely
  • Production database configured (PostgreSQL/SQL Server)
  • Database migrations applied
  • Connection strings stored in secure vault
  • HTTPS enabled with valid certificate
  • AllowedHosts restricted to production domains
  • Rate limiting configured
  • Logging configured with appropriate level (Warning/Error)
  • Monitoring and alerting set up
  • Swagger disabled in production
  • Development auto-migration code removed
  • Security headers added
  • CORS configured (if needed)
  • Health checks implemented
  • Load balancer/reverse proxy configured
  • Backup strategy implemented
  • Key rotation procedure documented
  • Environment variable ASPNETCORE_ENVIRONMENT=Production set
  • appsettings.Production.json reviewed
  • No secrets in source control

Container Deployment

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["AuthService.csproj", "./"]
RUN dotnet restore "AuthService.csproj"
COPY . .
RUN dotnet build "AuthService.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "AuthService.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "AuthService.dll"]
version: '3.8'

services:
  authservice:
    image: authservice:latest
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - ConnectionStrings__DefaultConnection=Host=db;Database=authservice;Username=authuser;Password=${DB_PASSWORD}
      - Jwt__SecretKey=${JWT_SECRET_KEY}
    ports:
      - "5000:80"
      - "5001:443"
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:15
    environment:
      - POSTGRES_DB=authservice
      - POSTGRES_USER=authuser
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  postgres_data:

Post-Deployment

1

Verify deployment

  • Check health endpoint: curl https://yourdomain.com/health
  • Test authentication flow
  • Verify logs are being collected
  • Check monitoring dashboards
2

Performance testing

Load test critical endpoints:
  • /api/auth/login
  • /api/auth/refresh
  • /api/auth/me
3

Security audit

  • Run security scanner (OWASP ZAP, Burp Suite)
  • Verify no secrets in logs
  • Test rate limiting
  • Verify HTTPS enforcement