Skip to main content
The FullStackHero starter kit uses ASP.NET Core’s configuration system with strongly-typed options classes. All settings are defined in appsettings.json and can be overridden via environment variables.

Configuration Sources

Configuration is loaded in this order (later sources override earlier ones):
  1. appsettings.json (base settings)
  2. appsettings.{Environment}.json (environment-specific)
  3. User secrets (Development only)
  4. Environment variables
  5. Command-line arguments
In production, use environment variables or Azure Key Vault for sensitive settings like JwtOptions__SigningKey and connection strings.

Required Configuration (Production)

The API validates these settings on startup when ASPNETCORE_ENVIRONMENT=Production:
src/Playground/Playground.Api/Program.cs
if (builder.Environment.IsProduction())
{
    static void Require(IConfiguration config, string key)
    {
        if (string.IsNullOrWhiteSpace(config[key]))
        {
            throw new InvalidOperationException($"Missing required configuration '{key}' in Production.");
        }
    }

    var config = builder.Configuration;
    Require(config, "DatabaseOptions:ConnectionString");
    Require(config, "CachingOptions:Redis");
    Require(config, "JwtOptions:SigningKey");
}

Database Options

Configures Entity Framework Core and database provider.
appsettings.json
{
  "DatabaseOptions": {
    "Provider": "POSTGRESQL",
    "ConnectionString": "Server=localhost;Database=fsh;User Id=postgres;Password=password",
    "MigrationsAssembly": "FSH.Playground.Migrations.PostgreSQL"
  }
}
PropertyTypeDefaultDescription
ProviderstringPOSTGRESQLDatabase provider: POSTGRESQL or SQLSERVER
ConnectionStringstringrequiredFull database connection string
MigrationsAssemblystringrequiredAssembly containing EF Core migrations

Environment Variable Override

export DatabaseOptions__Provider=POSTGRESQL
export DatabaseOptions__ConnectionString="Server=prod-db;Database=fsh;User Id=app;Password=secret"
export DatabaseOptions__MigrationsAssembly="FSH.Playground.Migrations.PostgreSQL"
The MigrationsAssembly must match your database provider:
  • PostgreSQL: FSH.Playground.Migrations.PostgreSQL
  • SQL Server: FSH.Playground.Migrations.SqlServer

JWT Options

Configures JSON Web Token authentication for API access.
appsettings.json
{
  "JwtOptions": {
    "Issuer": "fsh.local",
    "Audience": "fsh.clients",
    "SigningKey": "replace-with-256-bit-secret-min-32-chars",
    "AccessTokenMinutes": 2,
    "RefreshTokenDays": 7
  }
}
PropertyTypeDefaultDescription
Issuerstringfsh.localToken issuer claim (iss)
Audiencestringfsh.clientsToken audience claim (aud)
SigningKeystringrequiredSymmetric signing key (min 32 characters, 256 bits)
AccessTokenMinutesint2Access token lifetime in minutes
RefreshTokenDaysint7Refresh token lifetime in days
Security critical:
  • Generate a strong random key: openssl rand -base64 32
  • Never commit the signing key to version control
  • Use different keys per environment
  • In production, access tokens default to 2 minutes for security; increase to 15-60 minutes based on requirements

Environment Variable Override

export JwtOptions__SigningKey="$(openssl rand -base64 32)"
export JwtOptions__AccessTokenMinutes=15
export JwtOptions__RefreshTokenDays=30

Caching Options

Configures distributed caching with Redis.
appsettings.json
{
  "CachingOptions": {
    "Redis": ""
  }
}
PropertyTypeDefaultDescription
Redisstring"" (empty)Redis connection string. If empty, uses in-memory cache

Behavior

  • Empty string: Uses IMemoryCache (in-process caching)
  • Connection string: Uses IDistributedCache with StackExchange.Redis

Environment Variable Override

# Local Redis
export CachingOptions__Redis="localhost:6379"

# Azure Cache for Redis
export CachingOptions__Redis="mycache.redis.cache.windows.net:6380,password=key,ssl=True,abortConnect=False"

# AWS ElastiCache
export CachingOptions__Redis="master.my-cluster.abcdef.use1.cache.amazonaws.com:6379"
Distributed caching is required for:
  • Multi-instance deployments (horizontal scaling)
  • Shared session state
  • Rate limiting across instances

OpenTelemetry Options

Configures observability with OpenTelemetry (traces, metrics, logs).
appsettings.json
{
  "OpenTelemetryOptions": {
    "Enabled": true,
    "Tracing": {
      "Enabled": true
    },
    "Metrics": {
      "Enabled": true,
      "MeterNames": [
        "FSH.Modules.Identity",
        "FSH.Modules.Multitenancy",
        "FSH.Modules.Auditing"
      ]
    },
    "Exporter": {
      "Otlp": {
        "Enabled": true,
        "Endpoint": "http://localhost:4317",
        "Protocol": "grpc"
      }
    },
    "Jobs": { "Enabled": true },
    "Mediator": { "Enabled": true },
    "Http": {
      "Histograms": { "Enabled": true }
    },
    "Data": {
      "FilterEfStatements": true,
      "FilterRedisCommands": true
    }
  }
}
PropertyTypeDefaultDescription
EnabledbooltrueMaster switch for OpenTelemetry
Tracing.EnabledbooltrueEnable distributed tracing
Metrics.EnabledbooltrueEnable metrics collection
Metrics.MeterNamesstring[][...]Module-specific meters to collect
Exporter.Otlp.EnabledbooltrueEnable OTLP export
Exporter.Otlp.Endpointstringhttp://localhost:4317OTLP gRPC endpoint
Exporter.Otlp.ProtocolstringgrpcProtocol: grpc or http/protobuf
Jobs.EnabledbooltrueTrace Hangfire jobs
Mediator.EnabledbooltrueTrace Mediator commands/queries
Http.Histograms.EnabledbooltrueCollect request duration histograms
Data.FilterEfStatementsbooltrueFilter verbose EF Core SQL logs
Data.FilterRedisCommandsbooltrueFilter verbose Redis command logs

Supported OTLP Endpoints

export OpenTelemetryOptions__Exporter__Otlp__Endpoint="http://jaeger:4317"

CORS Options

Configures Cross-Origin Resource Sharing for API access.
appsettings.json
{
  "CorsOptions": {
    "AllowAll": false,
    "AllowedOrigins": [
      "https://localhost:4200",
      "https://localhost:7140"
    ],
    "AllowedHeaders": ["content-type", "authorization"],
    "AllowedMethods": ["GET", "POST", "PUT", "DELETE"]
  }
}
PropertyTypeDefaultDescription
AllowAllboolfalseAllow all origins (dangerous, dev only)
AllowedOriginsstring[][...]Whitelist of allowed origins
AllowedHeadersstring[][...]Allowed request headers
AllowedMethodsstring[][...]Allowed HTTP methods
Never set AllowAll: true in production. It disables CORS protection and allows any origin to access your API.

Environment Variable Override

export CorsOptions__AllowedOrigins__0="https://app.example.com"
export CorsOptions__AllowedOrigins__1="https://admin.example.com"

OpenAPI Options

Configures Swagger/OpenAPI documentation.
appsettings.json
{
  "OpenApiOptions": {
    "Enabled": true,
    "Title": "FSH PlayGround API",
    "Version": "v1",
    "Description": "The FSH Starter Kit API for Modular/Multitenant Architecture.",
    "Contact": {
      "Name": "Mukesh Murugan",
      "Url": "https://codewithmukesh.com",
      "Email": "[email protected]"
    },
    "License": {
      "Name": "MIT License",
      "Url": "https://opensource.org/licenses/MIT"
    }
  }
}
PropertyTypeDefaultDescription
EnabledbooltrueEnable OpenAPI generation
TitlestringFSH PlayGround APIAPI title in Swagger UI
Versionstringv1API version
Descriptionstring...API description
Contact.*object{...}Contact information
License.*object{...}License information
Access Swagger UI at: https://localhost:5285/scalar

Hangfire Options

Configures the Hangfire dashboard for background jobs.
appsettings.json
{
  "HangfireOptions": {
    "Username": "admin",
    "Password": "Secure1234!Me",
    "Route": "/jobs"
  }
}
PropertyTypeDefaultDescription
UsernamestringadminDashboard login username
PasswordstringSecure1234!MeDashboard login password
Routestring/jobsDashboard URL path
Change the default password before deploying to production!
Access dashboard at: https://localhost:5285/jobs

Rate Limiting Options

Configures rate limiting for API endpoints.
appsettings.json
{
  "RateLimitingOptions": {
    "Enabled": false,
    "Global": {
      "PermitLimit": 100,
      "WindowSeconds": 60,
      "QueueLimit": 0
    },
    "Auth": {
      "PermitLimit": 10,
      "WindowSeconds": 60,
      "QueueLimit": 0
    }
  }
}
PropertyTypeDefaultDescription
EnabledboolfalseMaster switch for rate limiting
Global.PermitLimitint100Max requests per window (global)
Global.WindowSecondsint60Time window in seconds
Global.QueueLimitint0Queue size when limit exceeded
Auth.PermitLimitint10Max auth requests per window
Auth.WindowSecondsint60Auth window in seconds
The Auth policy applies specifically to authentication endpoints (/api/v1/identity/tokens) to prevent brute force attacks.

Mail Options

Configures email sending via SMTP or SendGrid.
appsettings.json
{
  "MailOptions": {
    "UseSendGrid": false,
    "From": "[email protected]",
    "DisplayName": "Mukesh Murugan",
    "SMTP": {
      "Host": "smtp.ethereal.email",
      "Port": 587,
      "UserName": "[email protected]",
      "Password": "rqD44sq5P6U2UDCqD1"
    },
    "SendGrid": {
      "ApiKey": "your-sendgrid-api-key",
      "From": "[email protected]",
      "DisplayName": "FSH SendGrid"
    }
  }
}
PropertyTypeDefaultDescription
UseSendGridboolfalseUse SendGrid instead of SMTP
FromstringrequiredSender email address
DisplayNamestringrequiredSender display name
SMTP.HoststringrequiredSMTP server hostname
SMTP.Portint587SMTP port (typically 587 for TLS)
SMTP.UserNamestringrequiredSMTP username
SMTP.PasswordstringrequiredSMTP password
SendGrid.ApiKeystringrequiredSendGrid API key

Test Email Setup (Ethereal)

The default configuration uses Ethereal Email for testing:

Password Policy

Configures password expiration and history tracking.
appsettings.json
{
  "PasswordPolicy": {
    "PasswordHistoryCount": 5,
    "PasswordExpiryDays": 90,
    "PasswordExpiryWarningDays": 14,
    "EnforcePasswordExpiry": true
  }
}
PropertyTypeDefaultDescription
PasswordHistoryCountint5Prevent reusing last N passwords
PasswordExpiryDaysint90Force password change after N days
PasswordExpiryWarningDaysint14Warn user N days before expiry
EnforcePasswordExpirybooltrueEnable/disable password expiration

Multitenancy Options

Configures multi-tenant behavior.
appsettings.json
{
  "MultitenancyOptions": {
    "RunTenantMigrationsOnStartup": true
  }
}
PropertyTypeDefaultDescription
RunTenantMigrationsOnStartupbooltrueAuto-apply migrations to all tenant databases on startup
In production with many tenants, consider setting this to false and running migrations via a background job or manual process.

Storage Options

Configures file storage provider.
appsettings.json
{
  "Storage": {
    "Provider": "local"
  }
}
PropertyTypeDefaultDescription
ProviderstringlocalStorage provider: local, s3, or azure

Local Storage

Files stored in wwwroot/uploads/ directory.

AWS S3

{
  "Storage": {
    "Provider": "s3",
    "S3": {
      "BucketName": "fsh-storage",
      "Region": "us-east-1",
      "AccessKey": "your-access-key",
      "SecretKey": "your-secret-key"
    }
  }
}

Azure Blob Storage

{
  "Storage": {
    "Provider": "azure",
    "Azure": {
      "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=...",
      "ContainerName": "fsh-storage"
    }
  }
}

Security Headers Options

Configures Content Security Policy (CSP) and security headers.
appsettings.json
{
  "SecurityHeadersOptions": {
    "Enabled": true,
    "ExcludedPaths": ["/scalar", "/openapi"],
    "AllowInlineStyles": true,
    "ScriptSources": [],
    "StyleSources": []
  }
}
PropertyTypeDefaultDescription
EnabledbooltrueEnable security headers
ExcludedPathsstring[]["/scalar", "/openapi"]Paths exempt from CSP
AllowInlineStylesbooltrueAllow inline <style> tags
ScriptSourcesstring[][]Additional allowed script sources
StyleSourcesstring[][]Additional allowed style sources

Serilog Configuration

The starter kit uses Serilog for structured logging:
appsettings.json
{
  "Serilog": {
    "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.OpenTelemetry"],
    "Enrich": [
      "FromLogContext",
      "WithMachineName",
      "WithThreadId",
      "WithCorrelationId",
      "WithProcessId",
      "WithProcessName"
    ],
    "MinimumLevel": {
      "Default": "Debug"
    },
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "restrictedToMinimumLevel": "Information"
        }
      },
      {
        "Name": "OpenTelemetry",
        "Args": {
          "endpoint": "http://localhost:4317",
          "protocol": "grpc",
          "resourceAttributes": {
            "service.name": "Playground.Api"
          }
        }
      }
    ]
  }
}
Logs are:
  • Written to console with Information level
  • Exported to OpenTelemetry endpoint
  • Enriched with machine name, thread ID, correlation ID, process ID

Environment-Specific Configuration

Override settings per environment:
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Debug"
    }
  },
  "DatabaseOptions": {
    "ConnectionString": "Server=localhost;Database=fsh_dev;..."
  }
}

Configuration Validation

The API validates configuration on startup using Data Annotations:
Example: JwtOptions
[Required]
public string SigningKey { get; set; } = string.Empty;

[Range(1, 1440)]
public int AccessTokenMinutes { get; set; } = 60;
Invalid configuration throws OptionsValidationException on startup.

Next Steps

Project Structure

Understand the codebase organization

Database

Learn about EF Core setup

Security

Configure JWT authentication

Deployment

Deploy to production

Build docs developers (and LLMs) love