CSRF Prevention
CSRF attacks have been losing popularity over the past years, but they are still a possible way your application security can suffer. If you are using any cookie-based authentication, or any sort of authentication wherein you are not sending your authentication in a custom header, this concerns you.
Fortunately, TSec provides a simple CSRF prevention middleware.
In short, to guard against this vulnerability, all you need to do is use the middleware to set the token cookie, and
send such a token along with a custom header value. Given that an attacker forging a request cannot access the values
of a cookie due to same-origin policy, this simple mechanism will guard against CSRF
.
With good application design, you should only need to
guard your unsafe methods,
aka any http methods that could possibly make any changes to data or alter state, as this is what a
CSRF attacker is after. The validate
method takes a
predicate Request[F] => Boolean
, which defaults to _.methods.isSafe
. Any action which results in true
for
the predicate will skip the csrf check, and embed a new token if there isn’t one. It is highly recommended you
leave the predicate as is, unless you must make exceptions for specific routes that should be csrf-check free.
I.e If you mutate in a GET
request (god forbid), you might want to alter the predicate to csrf check GET
s as well.
Please, however, follow proper design principles, and keep idempotent methods idempotent.
All you need to use the CSRF middleware for tsec is:
- An
F: Sync
- Choose between one of HMACSHA1, HMACSHA256, HMACSHA384 or HMACSHA512. Recommended default: HMACSHA1, or 256.
- A MacSigningKey
- An endpoint where you can give a token to a user, either by default using
withNewToken
or directly into the response usingembed
- (Optional) A condition which does not csrf-validate requests that cause it to be true.
A truncated signature of the class looks like this:
final class TSecCSRF[F[_], A: MacTag: ByteEV] private[tsec] (
key: MacSigningKey[A],
val headerName: String,
val cookieName: String,
val tokenLength: Int,
clock: Clock
)
The apply
method on this new class is of type:
type CSRFMiddleware[F[_]] =
Middleware[OptionT[F, ?], Request[F], Response[F], Request[F], Response[F]]
Thus, you can use it as such:
import cats._
// import cats._
import cats.implicits._
// import cats.implicits._
import cats.effect.IO
// import cats.effect.IO
import tsec.mac.jca._
// import tsec.mac.jca._
import tsec.csrf.TSecCSRF
// import tsec.csrf.TSecCSRF
import org.http4s._
// import org.http4s._
import org.http4s.dsl.io._
// import org.http4s.dsl.io._
val newKey = HMACSHA256.unsafeGenerateKey
// newKey: tsec.mac.jca.MacSigningKey[tsec.mac.jca.HMACSHA256] = javax.crypto.spec.SecretKeySpec@fa77cfa0
val tsecCSRF = TSecCSRF[IO, HMACSHA256](newKey)
// tsecCSRF: tsec.csrf.TSecCSRF[cats.effect.IO,tsec.mac.jca.HMACSHA256] = tsec.csrf.TSecCSRF@5ae51222
val baseService: HttpRoutes[IO] = {
HttpRoutes.of[IO] {
case GET -> Root =>
Ok()
}
}
// baseService: org.http4s.HttpRoutes[cats.effect.IO] = Kleisli(org.http4s.HttpRoutes$$$Lambda$1808/926588054@3cae82a)
// This endpoint now provides a user with a new csrf token.
val dummyService: HttpRoutes[IO] = tsecCSRF.withNewToken(baseService)
// dummyService: org.http4s.HttpRoutes[cats.effect.IO] = Kleisli(cats.data.Kleisli$$$Lambda$1812/852390833@488f1562)
//This endpoint is csrf checked
val dummyServiceChecked: HttpRoutes[IO] = tsecCSRF.validate()(baseService)
// dummyServiceChecked: org.http4s.HttpRoutes[cats.effect.IO] = Kleisli(tsec.csrf.TSecCSRF$$Lambda$1815/1251934839@196523ea)