Version: 1.0 Date: February 10, 2026 Purpose: Provide complete environment configuration templates for all components
- API Environment Configuration
- SPA Environment Configuration
- WPF Environment Configuration
- Development vs Production Configurations
- Security Best Practices
File: FamilyTogether.API/appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning"
}
},
"AllowedHosts": "*",
"ApplicationInsights": {
"InstrumentationKey": ""
}
}File: FamilyTogether.API/appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Information",
"Microsoft.EntityFrameworkCore": "Information"
}
},
"ConnectionStrings": {
"Supabase": "Host=db.xxxxxxxxxxxxx.supabase.co;Database=postgres;Username=postgres;Password=YOUR_DEV_PASSWORD_HERE"
},
"Supabase": {
"Url": "https://xxxxxxxxxxxxx.supabase.co",
"AnonKey": "YOUR_SUPABASE_ANON_KEY_HERE",
"ServiceRoleKey": "YOUR_SUPABASE_SERVICE_ROLE_KEY_HERE",
"JwtSecret": "YOUR_SUPABASE_JWT_SECRET_HERE"
},
"Jwt": {
"Issuer": "https://xxxxxxxxxxxxx.supabase.co/auth/v1",
"Audience": "authenticated",
"ExpiryMinutes": 60,
"ValidateIssuer": true,
"ValidateAudience": true,
"ValidateLifetime": true,
"ValidateIssuerSigningKey": true
},
"Cors": {
"AllowedOrigins": [
"http://localhost:8000",
"http://localhost:3000",
"http://127.0.0.1:8000"
]
},
"RateLimit": {
"Enabled": false,
"GeneralLimit": 100,
"AuthLimit": 5,
"SyncLimit": 2,
"WindowMinutes": 1
},
"Sync": {
"MaxChangesPerRequest": 100,
"ConflictResolutionStrategy": "LastWriteWins",
"EnableDetailedLogging": true
}
}File: FamilyTogether.API/appsettings.Production.json
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Error"
}
},
"ConnectionStrings": {
"Supabase": "Host=db.xxxxxxxxxxxxx.supabase.co;Database=postgres;Username=postgres;Password=PRODUCTION_PASSWORD_REPLACE_ME"
},
"Supabase": {
"Url": "https://xxxxxxxxxxxxx.supabase.co",
"AnonKey": "PRODUCTION_ANON_KEY_REPLACE_ME",
"ServiceRoleKey": "PRODUCTION_SERVICE_ROLE_KEY_REPLACE_ME",
"JwtSecret": "PRODUCTION_JWT_SECRET_REPLACE_ME"
},
"Jwt": {
"Issuer": "https://xxxxxxxxxxxxx.supabase.co/auth/v1",
"Audience": "authenticated",
"ExpiryMinutes": 60,
"ValidateIssuer": true,
"ValidateAudience": true,
"ValidateLifetime": true,
"ValidateIssuerSigningKey": true
},
"Cors": {
"AllowedOrigins": [
"https://familytogether.netlify.app",
"https://www.yourdomainhere.com"
]
},
"RateLimit": {
"Enabled": true,
"GeneralLimit": 100,
"AuthLimit": 5,
"SyncLimit": 2,
"WindowMinutes": 1
},
"Sync": {
"MaxChangesPerRequest": 100,
"ConflictResolutionStrategy": "LastWriteWins",
"EnableDetailedLogging": false
},
"Sentry": {
"Dsn": "https://your-sentry-dsn-here.ingest.sentry.io/project-id",
"Environment": "production",
"TracesSampleRate": 0.1
}
}File: FamilyTogether.API/.env.example
# Supabase Configuration
SUPABASE_URL=https://xxxxxxxxxxxxx.supabase.co
SUPABASE_ANON_KEY=your_anon_key_here
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key_here
SUPABASE_JWT_SECRET=your_jwt_secret_here
# Database Connection
DATABASE_CONNECTION_STRING=Host=db.xxxxxxxxxxxxx.supabase.co;Database=postgres;Username=postgres;Password=your_password_here
# JWT Configuration
JWT_ISSUER=https://xxxxxxxxxxxxx.supabase.co/auth/v1
JWT_AUDIENCE=authenticated
JWT_EXPIRY_MINUTES=60
# CORS Configuration (comma-separated)
CORS_ALLOWED_ORIGINS=http://localhost:8000,http://localhost:3000
# Application Settings
ASPNETCORE_ENVIRONMENT=Development
ASPNETCORE_URLS=http://localhost:5000;https://localhost:5001
# Rate Limiting
RATE_LIMIT_ENABLED=false
RATE_LIMIT_GENERAL=100
RATE_LIMIT_AUTH=5
RATE_LIMIT_SYNC=2
# Sync Settings
SYNC_MAX_CHANGES_PER_REQUEST=100
SYNC_CONFLICT_RESOLUTION=LastWriteWins
SYNC_DETAILED_LOGGING=true
# Monitoring (Optional)
SENTRY_DSN=
SENTRY_ENVIRONMENT=developmentConfigure these in Railway Dashboard:
SUPABASE_URL=https://xxxxxxxxxxxxx.supabase.co
SUPABASE_ANON_KEY=<your_anon_key>
SUPABASE_SERVICE_ROLE_KEY=<your_service_role_key>
SUPABASE_JWT_SECRET=<your_jwt_secret>
DATABASE_CONNECTION_STRING=<your_connection_string>
JWT_ISSUER=https://xxxxxxxxxxxxx.supabase.co/auth/v1
JWT_AUDIENCE=authenticated
JWT_EXPIRY_MINUTES=60
CORS_ALLOWED_ORIGINS=https://familytogether.netlify.app
ASPNETCORE_ENVIRONMENT=Production
RATE_LIMIT_ENABLED=true
SENTRY_DSN=<optional_sentry_dsn>
File: FamilyTogether.SPA/src/config.js
const CONFIG = {
// Supabase Configuration
supabaseUrl: 'https://xxxxxxxxxxxxx.supabase.co',
supabaseAnonKey: 'YOUR_SUPABASE_ANON_KEY_HERE',
// API Configuration
apiUrl: 'http://localhost:5000',
apiTimeout: 30000, // 30 seconds
// Sync Configuration
syncIntervalMs: 30000, // 30 seconds
syncRetryAttempts: 3,
syncRetryDelayMs: 1000, // Start with 1 second, exponential backoff
maxChangesPerSync: 100,
// IndexedDB Configuration
dbName: 'FamilyTogetherDB',
dbVersion: 1,
// Storage Configuration
maxLocalStorageMB: 50,
enableOfflineMode: true,
// UI Configuration
defaultTheme: 'light',
enableDebugMode: false,
// Feature Flags
features: {
offlineMode: true,
autoSync: true,
pushNotifications: false,
analytics: false
}
};
// Freeze to prevent modification
Object.freeze(CONFIG);
export default CONFIG;File: FamilyTogether.SPA/src/config.development.js
const DEV_CONFIG = {
// Override base config for development
apiUrl: 'http://localhost:5000',
enableDebugMode: true,
// More frequent sync for testing
syncIntervalMs: 10000, // 10 seconds
// Enable all features for development
features: {
offlineMode: true,
autoSync: true,
pushNotifications: true,
analytics: true
}
};
export default DEV_CONFIG;File: FamilyTogether.SPA/src/config.production.js
const PROD_CONFIG = {
// Override base config for production
apiUrl: 'https://familytogether-api.up.railway.app',
enableDebugMode: false,
// Standard sync interval
syncIntervalMs: 30000, // 30 seconds
// Disable experimental features
features: {
offlineMode: true,
autoSync: true,
pushNotifications: false, // Enable when ready
analytics: true // Enable with analytics service
}
};
export default PROD_CONFIG;File: FamilyTogether.SPA/.env.example
# Supabase Configuration
VITE_SUPABASE_URL=https://xxxxxxxxxxxxx.supabase.co
VITE_SUPABASE_ANON_KEY=your_anon_key_here
# API Configuration
VITE_API_URL=http://localhost:5000
# Application Settings
VITE_APP_NAME=FamilyTogether
VITE_APP_VERSION=1.0.0
VITE_ENVIRONMENT=development
# Feature Flags
VITE_ENABLE_OFFLINE_MODE=true
VITE_ENABLE_AUTO_SYNC=true
VITE_ENABLE_DEBUG=trueFile: FamilyTogether.SPA/netlify.toml
[build]
publish = "."
command = ""
# Production environment
[context.production]
[context.production.environment]
VITE_API_URL = "https://familytogether-api.up.railway.app"
VITE_ENVIRONMENT = "production"
VITE_ENABLE_DEBUG = "false"
# Development branch environment
[context.develop]
[context.develop.environment]
VITE_API_URL = "https://dev-familytogether-api.up.railway.app"
VITE_ENVIRONMENT = "development"
VITE_ENABLE_DEBUG = "true"
# Redirects for SPA routing
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
# Security Headers
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-XSS-Protection = "1; mode=block"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
Permissions-Policy = "geolocation=(), microphone=(), camera=()"
# Cache static assets
[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"File: FamilyTogether.WPF/App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!-- Supabase Configuration -->
<add key="SupabaseUrl" value="https://xxxxxxxxxxxxx.supabase.co" />
<add key="SupabaseAnonKey" value="YOUR_SUPABASE_ANON_KEY_HERE" />
<!-- API Configuration -->
<add key="ApiUrl" value="http://localhost:5000" />
<add key="ApiTimeout" value="30000" />
<!-- Database Configuration -->
<add key="DatabasePath" value="%APPDATA%\FamilyTogether\app.db" />
<!-- Sync Configuration -->
<add key="SyncIntervalMs" value="30000" />
<add key="SyncRetryAttempts" value="3" />
<add key="MaxChangesPerSync" value="100" />
<!-- Application Settings -->
<add key="EnableOfflineMode" value="true" />
<add key="EnableDebugMode" value="false" />
<add key="AutoStartSync" value="true" />
<!-- Logging -->
<add key="LogLevel" value="Information" />
<add key="LogPath" value="%APPDATA%\FamilyTogether\Logs" />
</appSettings>
<connectionStrings>
<add name="LocalDatabase"
connectionString="Data Source=%APPDATA%\FamilyTogether\app.db"
providerName="Microsoft.EntityFrameworkCore.Sqlite" />
</connectionStrings>
</configuration>File: FamilyTogether.WPF/appsettings.json
{
"Supabase": {
"Url": "https://xxxxxxxxxxxxx.supabase.co",
"AnonKey": "YOUR_SUPABASE_ANON_KEY_HERE"
},
"Api": {
"Url": "http://localhost:5000",
"Timeout": 30000
},
"Database": {
"Path": "%APPDATA%\\FamilyTogether\\app.db"
},
"Sync": {
"IntervalMs": 30000,
"RetryAttempts": 3,
"MaxChangesPerSync": 100
},
"Application": {
"EnableOfflineMode": true,
"EnableDebugMode": false,
"AutoStartSync": true
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning"
},
"LogPath": "%APPDATA%\\FamilyTogether\\Logs"
}
}File: FamilyTogether.WPF/appsettings.Production.json
{
"Api": {
"Url": "https://familytogether-api.up.railway.app",
"Timeout": 30000
},
"Application": {
"EnableDebugMode": false
},
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft": "Error"
}
}
}| Setting | Development | Production |
|---|---|---|
| API URL | http://localhost:5000 |
https://familytogether-api.up.railway.app |
| CORS Origins | localhost:8000, localhost:3000 |
familytogether.netlify.app |
| Log Level | Debug / Information |
Warning / Error |
| Rate Limiting | Disabled | Enabled |
| JWT Expiry | 60 minutes | 60 minutes |
| Sync Interval | 10-30 seconds | 30 seconds |
| Debug Mode | Enabled | Disabled |
| Detailed Logging | Enabled | Disabled |
| EF Logging | Information |
Error |
API (Program.cs):
var environment = builder.Environment.EnvironmentName;
if (environment == "Development")
{
// Development-specific configuration
builder.Services.AddCors(options =>
{
options.AddPolicy("Development", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
}
else
{
// Production-specific configuration
builder.Services.AddCors(options =>
{
options.AddPolicy("Production", policy =>
{
policy.WithOrigins(builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>())
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
}SPA (main.js):
// Detect environment
const environment = import.meta.env.MODE || 'production';
// Load appropriate config
let config;
if (environment === 'development') {
config = { ...CONFIG, ...DEV_CONFIG };
} else {
config = { ...CONFIG, ...PROD_CONFIG };
}
console.log(`Running in ${environment} mode`);WPF (App.xaml.cs):
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// Detect environment from command line args or config
var isDevelopment = e.Args.Contains("--dev") ||
ConfigurationManager.AppSettings["EnableDebugMode"] == "true";
if (isDevelopment)
{
// Load development settings
ConfigurationManager.AppSettings["ApiUrl"] = "http://localhost:5000";
}
}
}Add to .gitignore:
# API
appsettings.Development.json
appsettings.Production.json
.env
.env.local
.env.production
# SPA
.env
.env.local
.env.development.local
.env.production.local
config.production.js
# WPF
App.config
appsettings.json
appsettings.Production.json
API (Program.cs):
// Read from environment variables (Railway, Azure, etc.)
builder.Configuration.AddEnvironmentVariables();
var supabaseUrl = Environment.GetEnvironmentVariable("SUPABASE_URL")
?? builder.Configuration["Supabase:Url"];SPA (config.js):
// Read from Vite environment variables
const CONFIG = {
supabaseUrl: import.meta.env.VITE_SUPABASE_URL || 'fallback-url',
apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:5000'
};Rotation Schedule:
- JWT Secret: Every 90 days
- Supabase Service Role Key: Every 6 months
- Database Password: Every 90 days
API (Program.cs):
// Validate required configuration
var requiredSettings = new[] {
"Supabase:Url",
"Supabase:AnonKey",
"Supabase:JwtSecret",
"ConnectionStrings:Supabase"
};
foreach (var setting in requiredSettings)
{
if (string.IsNullOrEmpty(builder.Configuration[setting]))
{
throw new InvalidOperationException($"Required configuration '{setting}' is missing");
}
}- Copy
.env.exampleto.env(API & SPA) - Copy
appsettings.json.exampletoappsettings.Development.json(API) - Replace all
YOUR_*_HEREplaceholders with actual values - Verify Supabase project URL and keys
- Test database connection string
- Confirm CORS origins for local development
- Add
.envand sensitive config files to.gitignore
- Create
appsettings.Production.jsonwith production values - Configure Railway environment variables
- Configure Netlify environment variables
- Set production CORS origins
- Enable rate limiting
- Disable debug mode
- Set appropriate log levels
- Configure Sentry DSN (optional)
- Test production API URL from SPA
- Verify JWT token validation
- Test file upload to Supabase Storage
Check:
- Supabase URL format:
https://xxxxx.supabase.co - Anon key is correct (not service role key)
- Project is not paused (Supabase free tier)
Check:
- API CORS policy includes SPA origin
UseCors()called beforeUseAuthorization()in Program.cs- Netlify URL matches exactly (trailing slash matters)
Check:
- JWT Secret matches Supabase project
- Issuer format:
https://xxxxx.supabase.co/auth/v1 - Audience is
"authenticated" - Token hasn't expired (check client clock)
Check:
- File naming:
appsettings.Development.json(capital D) - File in correct directory (project root)
- "Copy to Output Directory" = "Copy if newer"
- Environment variable
ASPNETCORE_ENVIRONMENTset correctly
- Go to https://app.supabase.com
- Select your project
- Settings → API
- Copy:
- Project URL
- anon public key
- service_role key (keep secret!)
- Settings → API → JWT Settings
- Copy: JWT Secret
# Via Railway CLI
railway variables set SUPABASE_URL=https://xxxxx.supabase.co
railway variables set SUPABASE_ANON_KEY=your_key_here
# Or via Dashboard
# Project → Variables → Add Variable# Via Netlify CLI
netlify env:set VITE_API_URL https://your-api.railway.app
# Or via Dashboard
# Site → Site settings → Environment variablesEND OF ENVIRONMENT CONFIGURATION TEMPLATES