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 GETs 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 using embed
  • (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)