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

  1. Certificate Creation: A self-signed certificate is generated for localhost development
  2. Dual Endpoints: Both HTTP (port 5000) and HTTPS (port 5443) listeners are configured
  3. HSTS Configuration: The Add-KrHsts cmdlet enables HSTS with a 30-day policy, subdomain inclusion, and preload directive
  4. HTTPS Redirection: HTTP requests are automatically redirected to HTTPS with a 301 permanent redirect
  5. Route Registration: A simple test route is added to verify the security setup
  6. 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

  1. Max Age: How long browsers should remember the HSTS policy
  2. Include Subdomains: Whether the policy applies to all subdomains
  3. 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 -AllowInDevelopment to 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

  1. Run the sample:

    .\10.4-Https-Hsts.ps1 -Port 8080
    
  2. Test HTTP to HTTPS redirect:
    • Visit http://localhost:8080
    • Notice the redirect to https://localhost:8523
  3. Verify HSTS header:

    $response = Invoke-WebRequest -Uri "https://localhost:8523" -SkipCertificateCheck
    $response.Headers['Strict-Transport-Security']
    
  4. Check browser behavior:
    • After first HTTPS visit, try typing http://localhost:8523 in browser
    • Browser should automatically use HTTPS

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 -SkipCertificateCheck for testing only
  • Consider using mkcert for development certificates

Previous / Next

References