HTTP Strict Transport Security
Http4s provides a Middleware giving support for HTTP Strict Transport Security (HSTS).
The middleware is called HSTS
and simply adds a header to enable a HSTS security policy.
Though it is not enforced, HSTS only makes sense for an https
service.
Examples in this document have the following dependencies.
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-dsl" % http4sVersion,
"org.http4s" %% "http4s-server" % http4sVersion
)
And we need some imports.
import org.http4s._
import org.http4s.dsl.io._
import org.http4s.implicits._
import cats.effect.IO
If you're in a REPL, we also need a runtime:
import cats.effect.unsafe.IORuntime
implicit val runtime: IORuntime = cats.effect.unsafe.IORuntime.global
Let's make a simple service that will be exposed and wrapped with HSTS.
val service = HttpRoutes.of[IO] {
case _ => Ok("ok")
}
val request = Request[IO](Method.GET, uri"/")
// Do not call 'unsafeRunSync' in your code
val responseOk = service.orNotFound(request).unsafeRunSync()
// responseOk: Response[[A]IO[A]] = (
// = Status(code = 200),
// = HttpVersion(major = 1, minor = 1),
// = Headers(Content-Type: text/plain; charset=UTF-8, Content-Length: 2),
// = Strict(
// bytes = Chunk(
// bytes = View(
// at = scodec.bits.ByteVector$AtArray@69f720bc,
// offset = 0L,
// size = 2L
// )
// )
// ),
// = org.typelevel.vault.Vault@4f8d9e0c
// )
responseOk.headers
// res0: Headers = Headers(Content-Type: text/plain; charset=UTF-8, Content-Length: 2)
If we were to wrap this on the HSTS
middleware.
import org.http4s.server.middleware._
val hstsService = HSTS(service)
// Do not call 'unsafeRunSync' in your code
val responseHSTS = hstsService.orNotFound(request).unsafeRunSync()
// responseHSTS: Response[[A]IO[A]] = (
// = Status(code = 200),
// = HttpVersion(major = 1, minor = 1),
// = Headers(Content-Type: text/plain; charset=UTF-8, Content-Length: 2, Strict-Transport-Security: max-age=31536000; includeSubDomains),
// = Strict(
// bytes = Chunk(
// bytes = View(
// at = scodec.bits.ByteVector$AtArray@e0de6bd,
// offset = 0L,
// size = 2L
// )
// )
// ),
// = org.typelevel.vault.Vault@6789ae18
// )
responseHSTS.headers
// res1: Headers = Headers(Content-Type: text/plain; charset=UTF-8, Content-Length: 2, Strict-Transport-Security: max-age=31536000; includeSubDomains)
Now the response has the Strict-Transport-Security
header which will mandate browsers
supporting HSTS to always connect using https
.
As described in Middleware, services and middleware can be composed though HSTS is something you may want enabled across all your routes.
Configuration
By default HSTS
is configured to indicate that all requests during 1 year
should be done over https
and it will contain the includeSubDomains
directive by default.
If you want to preload
or change other default values you can pass a custom header, e.g.
import org.http4s.headers._
import scala.concurrent.duration._
val hstsHeader = `Strict-Transport-Security`
.unsafeFromDuration(30.days, includeSubDomains = true, preload = true)
val hstsServiceCustom = HSTS(service, hstsHeader)
// Do not call 'unsafeRunSync' in your code
val responseCustom = hstsServiceCustom.orNotFound(request).unsafeRunSync()
// responseCustom: Response[[A]IO[A]] = (
// = Status(code = 200),
// = HttpVersion(major = 1, minor = 1),
// = Headers(Content-Type: text/plain; charset=UTF-8, Content-Length: 2, Strict-Transport-Security: max-age=2592000; includeSubDomains; preload),
// = Strict(
// bytes = Chunk(
// bytes = View(
// at = scodec.bits.ByteVector$AtArray@6db73a94,
// offset = 0L,
// size = 2L
// )
// )
// ),
// = org.typelevel.vault.Vault@4764a054
// )
responseCustom.headers
// res2: Headers = Headers(Content-Type: text/plain; charset=UTF-8, Content-Length: 2, Strict-Transport-Security: max-age=2592000; includeSubDomains; preload)