JWT
Our JWT implementation addresses this vulnerability
through the scala type system. As all JWTs
are typed, both during encoding and decoding, there is no way to parse an arbitrary JWT for
an endpoint before knowing the expected algorithm, thus, we avoid the ol’ switcheroo.
The general imports for this module are as follows:
import cats.syntax.all._
import cats.effect.Sync
import tsec.jwt._
import tsec.jws.mac._
import tsec.mac.jca._
import scala.concurrent.duration._
To create JWT Claims, use the JWTClaims object:
scala> def claimsWithDuration[F[_]: Sync]: F[JWTClaims] = JWTClaims.withDuration[F](expiration = Some(10.minutes))
claimsWithDuration: [F[_]](implicit evidence$1: cats.effect.Sync[F])F[tsec.jwt.JWTClaims]
We can also add custom claims to a JWT object. Imagine we have some custom case class (though it can be any type):
import io.circe._
import io.circe.syntax._
import io.circe.generic.semiauto._
case class Doge(suchChars: String, much32Bits: Int, so64Bits: Long)
//Note: Normally this would be in the companion
implicit val encoder: Encoder[Doge] = deriveEncoder[Doge]
implicit val decoder: Decoder[Doge] = deriveDecoder[Doge]
val WowSuchClaim = "Doge"
We can add it via explicit json serialization as such:
scala> JWTClaims(customFields = Seq(WowSuchClaim -> Doge("w00f", 8008135, 80085L).asJson))
res1: tsec.jwt.JWTClaims =
JWTClaims(None,None,None,None,None,None,Some(d2f4f910638cfde122f07edb7ad6dcca),object[iss -> null,sub -> null,aud -> null,exp -> null,nbf -> null,iat -> null,jti -> "d2f4f910638cfde122f07edb7ad6dcca",Doge -> {
"suchChars" : "w00f",
"much32Bits" : 8008135,
"so64Bits" : 80085
}])
The JWT module comes with two ways to work with JWT by default interpreting
into a Target F[_]
with a Sync[F]
, or interpreting into Either[Throwable, A]
if you do not like writing pure code.
/** You can interpret into any target Monad with an instance of Sync[F] using JwtMac */
def jwtMonadic[F[_]: Sync]: F[JWTMac[HMACSHA256]] =
for {
key <- HMACSHA256.generateKey[F]
claims <- JWTClaims.withDuration[F](expiration = Some(10.minutes))
jwt <- JWTMac.build[F, HMACSHA256](claims, key) //You can sign and build a jwt object directly
verifiedFromObj <- JWTMac.verifyFromInstance[F, HMACSHA256](jwt, key) //Verify from an object directly
stringjwt <- JWTMac.buildToString[F, HMACSHA256](claims, key) //Or build it straight to string
isverified <- JWTMac.verifyFromString[F, HMACSHA256](stringjwt, key) //You can verify straight from a string
parsed <- JWTMac.verifyAndParse[F, HMACSHA256](stringjwt, key) //Or verify and return the actual instance
} yield parsed
import java.time.Instant
/** Or using an impure either interpreter */
val impureClaims = JWTClaims(expiration = Some(Instant.now.plusSeconds(10.minutes.toSeconds)))
val jwt: Either[Throwable, JWTMac[HMACSHA256]] = for {
key <- HMACSHA256.generateKey[MacErrorM]
jwt <- JWTMacImpure.build[HMACSHA256](impureClaims, key) //You can sign and build a jwt object directly
verifiedFromObj <- JWTMacImpure.verifyFromInstance[HMACSHA256](jwt, key)
stringjwt <- JWTMacImpure.buildToString[HMACSHA256](impureClaims, key) //Or build it straight to string
isverified <- JWTMacImpure.verifyFromString[HMACSHA256](stringjwt, key) //You can verify straight from a string
parsed <- JWTMacImpure.verifyAndParse[HMACSHA256](stringjwt, key) //Or verify and return the actual instance
} yield parsed