HTTPS Strict Transport Security (HSTS)
HTTP Strict Transport Security (HSTS) is a web security policy mechanism that helps protect websites against protocol downgrade attacks and cookie hijacking. When enabled, HSTS tells browsers to only interact with your site using secure HTTPS connections, never HTTP.
Quick Start
Here’s a basic example that sets up HTTPS with HSTS:
<# Create a self-signed development certificate and start HTTPS listener. FileName: 10.4-Https-Hsts.ps1 #> param( [int]$Port = 5000, [IPAddress]$IPAddress = [IPAddress]::Loopback )
Initialize Kestrun root directory
Initialize-KrRoot -Path $PSScriptRoot
Configure default logging
New-KrLogger | Set-KrLoggerLevel -Value Debug | Add-KrSinkConsole | Register-KrLogger -Name ‘myLogger’ -SetAsDefault
Create a self-signed cert for localhost (RSA 2048 by default)
$cert = New-KrSelfSignedCertificate -DnsNames localhost, 127.0.0.1 -Exportable -ValidDays 30
Configure HTTPS listener with the certificate
New-KrServer -Name “HTTPS HSTS Demo” Add-KrEndpoint -Port ($Port) -IPAddress $IPAddress Add-KrEndpoint -Port ($Port + 443) -IPAddress $IPAddress -X509Certificate $cert
Add HSTS middleware with development support
Add-KrHsts -MaxAgeDays 30 -IncludeSubDomains -Preload -AllowInDevelopment Add-KrHttpsRedirection -RedirectStatusCode 301 -HttpsPort ($Port + 443)
Enable Kestrun configuration
Enable-KrConfiguration
Minimal route to verify HTTPS redirect works
Add-KrMapRoute -Verbs Get -Pattern “/” -ScriptBlock { Write-KrTextResponse “hello https” }
Start-KrServer -CloseLogsOnExit
Full source
File: pwsh/tutorial/examples/10.4-Https-Hsts.ps1
<#
Create a self-signed development certificate and start HTTPS listener.
FileName: 10.4-Https-Hsts.ps1
#>
param(
[int]$Port = 5000,
[IPAddress]$IPAddress = [IPAddress]::Loopback
)
# Initialize Kestrun root directory
Initialize-KrRoot -Path $PSScriptRoot
# Configure default logging
New-KrLogger |
Set-KrLoggerLevel -Value Debug |
Add-KrSinkConsole |
Register-KrLogger -Name 'myLogger' -SetAsDefault
# Create a self-signed cert for localhost (RSA 2048 by default)
$cert = New-KrSelfSignedCertificate -DnsNames localhost, 127.0.0.1 -Exportable -ValidDays 30
# Configure HTTPS listener with the certificate
New-KrServer -Name "HTTPS HSTS Demo"
Add-KrEndpoint -Port ($Port) -IPAddress $IPAddress
Add-KrEndpoint -Port ($Port + 443) -IPAddress $IPAddress -X509Certificate $cert
# Add HSTS middleware with development support
Add-KrHsts -MaxAgeDays 30 -IncludeSubDomains -Preload -AllowInDevelopment
Add-KrHttpsRedirection -RedirectStatusCode 301 -HttpsPort ($Port + 443)
# Enable Kestrun configuration
Enable-KrConfiguration
# Minimal route to verify HTTPS redirect works
Add-KrMapRoute -Verbs Get -Pattern "/" -ScriptBlock { Write-KrTextResponse "hello https" }
Start-KrServer -CloseLogsOnExit
Step-by-step
- Certificate Creation: A self-signed certificate is generated for localhost development
- Dual Endpoints: Both HTTP (port 5000) and HTTPS (port 5443) listeners are configured
- HSTS Configuration: The
Add-KrHstscmdlet enables HSTS with a 30-day policy, subdomain inclusion, and preload directive - HTTPS Redirection: HTTP requests are automatically redirected to HTTPS with a 301 permanent redirect
- Route Registration: A simple test route is added to verify the security setup
- Browser Enforcement: After the first HTTPS visit, browsers automatically upgrade HTTP requests to HTTPS
Understanding HSTS
HSTS works by sending a special header (Strict-Transport-Security) that instructs browsers to:
- Only access the site over HTTPS for a specified period
- Automatically upgrade HTTP requests to HTTPS
- Refuse connections if the certificate is invalid
Key Components
- Max Age: How long browsers should remember the HSTS policy
- Include Subdomains: Whether the policy applies to all subdomains
- Preload: Whether the site should be included in browser preload lists
Configuration Options
Basic HSTS Setup
# Default 30-day policy
Add-KrHsts
# Custom duration
Add-KrHsts -MaxAgeDays 365
Advanced Configuration
# Full HSTS configuration with all options
Add-KrHsts -MaxAgeDays 365 -IncludeSubDomains -Preload -ExcludedHosts @('dev.example.com', 'staging.example.com')
Development and Testing
# Enable HSTS in development environments
Add-KrHsts -MaxAgeDays 30 -IncludeSubDomains -Preload -AllowInDevelopment
Note: By default, ASP.NET Core excludes localhost and development hosts from HSTS for security. Use
-AllowInDevelopmentto enable HSTS for testing scenarios.
Using HstsOptions Object
# Create custom options object
$hstsOptions = [Microsoft.AspNetCore.HttpsPolicy.HstsOptions]::new()
$hstsOptions.MaxAge = [TimeSpan]::FromDays(90)
$hstsOptions.IncludeSubDomains = $true
$hstsOptions.Preload = $true
Add-KrHsts -Options $hstsOptions
Combining with HTTPS Redirection
HSTS works best when combined with HTTPS redirection:
# Set up both HTTPS and HTTP endpoints
Add-KrEndpoint -Port 80 -IPAddress $IPAddress
Add-KrEndpoint -Port 443 -IPAddress $IPAddress -X509Certificate $cert
# Add HTTPS redirection first
Add-KrHttpsRedirection -RedirectStatusCode 301 -HttpsPort 443
# Then add HSTS
Add-KrHsts -MaxAgeDays 365 -IncludeSubDomains -Preload
Security Considerations
Preload List
The preload directive tells browsers to include your site in their built-in HSTS preload list:
Add-KrHsts -MaxAgeDays 365 -IncludeSubDomains -Preload
Important: Once preloaded, removal from browser lists can be slow and difficult.
Excluded Hosts
You can exclude specific hosts from HSTS (useful for development):
Add-KrHsts -MaxAgeDays 365 -ExcludedHosts @('localhost', '127.0.0.1', 'dev.mysite.com')
Certificate Requirements
HSTS requires valid SSL certificates. Use self-signed certificates only for development:
# Development certificate
$cert = New-KrSelfSignedCertificate -DnsNames localhost, 127.0.0.1 -ValidDays 30
# Production should use CA-issued certificates
$cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object Subject -Like "*mysite.com*"
Try It
-
Run the sample:
.\10.4-Https-Hsts.ps1 -Port 8080 - Test HTTP to HTTPS redirect:
- Visit
http://localhost:8080 - Notice the redirect to
https://localhost:8523
- Visit
-
Verify HSTS header:
$response = Invoke-WebRequest -Uri "https://localhost:8523" -SkipCertificateCheck $response.Headers['Strict-Transport-Security'] - Check browser behavior:
- After first HTTPS visit, try typing
http://localhost:8523in browser - Browser should automatically use HTTPS
- After first HTTPS visit, try typing
Common Patterns
Development Environment
# HSTS enabled for development testing
Add-KrHsts -MaxAgeDays 1 -IncludeSubDomains -AllowInDevelopment
Add-KrHttpsRedirection -RedirectStatusCode 307 # Temporary redirect
Alternatively, exclude specific development hosts:
# Exclude only specific development hosts
Add-KrHsts -MaxAgeDays 1 -ExcludedHosts @('dev.example.com', 'staging.example.com')
Add-KrHttpsRedirection -RedirectStatusCode 307
Production Environment
# Strict HSTS for production
Add-KrHsts -MaxAgeDays 365 -IncludeSubDomains -Preload
Add-KrHttpsRedirection -RedirectStatusCode 301 # Permanent redirect
API Endpoints
# HSTS for API with longer duration
Add-KrHsts -MaxAgeDays 730 # 2 years
Add-KrHttpsRedirection -RedirectStatusCode 301
Troubleshooting
HSTS not working in browser:
- Clear browser cache and cookies for the site
- Check that HTTPS endpoint is accessible
- Verify certificate is valid
Development issues with HSTS:
- Use shorter MaxAge periods during development
- Add development domains to ExcludedHosts
- Use temporary redirects (307) instead of permanent (301)
Certificate warnings:
- Self-signed certificates will show warnings
- Use
-SkipCertificateCheckfor testing only - Consider using
mkcertfor development certificates