Validation & Security
Learn how to validate input, implement CORS, authentication, rate limiting, and security headers.
Basic Input Validation
Validate request data before processing:
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
open Suave.RequestErrors
let validateEmail (email: string) =
email.Contains("@") && email.Contains(".")
let app =
choose [
POST >=> path "/subscribe" >=> fun ctx ->
match ctx.request.formData "email" with
| Choice1Of2 email when validateEmail email ->
OK (sprintf "Subscribed: %s" email) ctx
| Choice1Of2 _ ->
BAD_REQUEST "Invalid email address" ctx
| Choice2Of2 _ ->
BAD_REQUEST "Missing email field" ctx
]
CORS (Cross-Origin Resource Sharing)
Enable CORS for specific domains:
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
open Suave.CORS
let corsConfig =
{ defaultCORSConfig with
allowedUris = InclusiveOption.Some ["http://localhost:3000"; "https://example.com"] }
let app =
choose [
GET >=> path "/api/data" >=> cors corsConfig >=> OK "CORS enabled"
]
Security Headers
Add important security headers to responses:
open Suave
open Suave.Operators
open Suave.Writers
open Suave.Filters
open Suave.Successful
let setSecurityHeaders =
setHeader "X-Content-Type-Options" "nosniff" >=>
setHeader "X-Frame-Options" "SAMEORIGIN" >=>
setHeader "X-XSS-Protection" "1; mode=block" >=>
setHeader "Strict-Transport-Security" "max-age=31536000; includeSubDomains"
let app =
choose [
path "/" >=> setSecurityHeaders >=> OK "Secure content"
]
Basic Authentication
Implement basic HTTP authentication:
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
open Suave.Authentication
let check (username, password) =
username = "admin" && password = "secret"
let app =
choose [
GET >=> path "/admin" >=> Authentication.authenticateBasic check (OK "Admin area")
]
Rate Limiting
Implement rate limiting to protect against abuse:
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
open Suave.RateLimit
// Simple rate limit: 10 requests per minute
let rateLimit10PerMin =
rateLimit (perMinute 10) (OK "Request successful")
// By user (using header)
let limitByUser =
rateLimit
(perMinute 100
|> withKeyExtractor (headerKeyExtractor "X-User-Id"))
(OK "User request successful")
let app =
choose [
path "/api/public" >=> rateLimit10PerMin
path "/api/private" >=> limitByUser
]
Request Size Limits
Limit request body size to prevent abuse:
open Suave
open Suave.Successful
let config =
{ defaultConfig with
bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" 8080 ]
maxContentLength = 1024 * 1024 // 1 MB limit
}
[<EntryPoint>]
let main argv =
startWebServer config (OK "Size limited")
0
Listen / bind timeout
listenTimeout controls how long Suave waits for the socket bind to finish (not per-request duration):
open Suave
open System
open Suave.Successful
let config =
{ defaultConfig with
bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" 8080 ]
listenTimeout = TimeSpan.FromSeconds(30.0)
}
[<EntryPoint>]
let main argv =
startWebServer config (OK "Configured")
0
Input Sanitization
Sanitize user input to prevent injection attacks:
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
open System.Net
open Suave.RequestErrors
let sanitize (input: string) =
WebUtility.HtmlEncode input
let app =
choose [
POST >=> path "/comment" >=> request (fun r ->
match r.formData "text" with
| Choice1Of2 text ->
OK (sprintf "Comment: %s" (sanitize text))
| Choice2Of2 _ ->
RequestErrors.BAD_REQUEST "Missing text")
]
HTTPS Only
Enforce HTTPS and redirect HTTP requests:
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
open Suave.Redirection
let enforceHttps : WebPart =
fun ctx ->
if ctx.request.binding.scheme.secure then
async { return Some ctx }
else
let url = "https://" + ctx.request.host + ctx.request.path
FOUND url ctx
let app =
choose [
enforceHttps >=> path "/" >=> OK "Secure connection"
]
Request Validation Pattern
Complete validation pattern for common scenarios:
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
open Suave.RequestErrors
type CreateUserRequest = { name: string; email: string; age: int }
let validateUser (req: CreateUserRequest) =
match req with
| _ when req.name.Length < 2 -> Result.Error "Name too short"
| _ when not (req.email.Contains "@") -> Result.Error "Invalid email"
| _ when req.age < 18 -> Result.Error "Must be 18+"
| _ -> Result.Ok req
let app =
choose [
POST >=> path "/users" >=> fun ctx ->
async {
try
// rawForm is already a byte[]
let body = ctx.request.rawForm
let json = System.Text.Encoding.UTF8.GetString(body)
let req = System.Text.Json.JsonSerializer.Deserialize<CreateUserRequest>(json)
match validateUser req with
| Result.Ok validReq ->
return! OK (sprintf "User created: %s" validReq.name) ctx
| Result.Error msg ->
return! BAD_REQUEST msg ctx
with _ ->
return! BAD_REQUEST "Invalid request" ctx
}
]