Testing
The jots-testing module depends on the core module and provides ScalaCheck generators and instances, and String interpolators for secrets. Importantly, the testing module also allows us to create a VerifiedJwt from any SignedJwt for testing purposes. This means the module should generally only be used for testing purposes.
Getting Started
To get started with sbt, add the following line to your build.sbt file.
libraryDependencies += "se.vlovgr" %% "jots-testing" % "0.4.0" % Test
If you are using Scala.js or Scala Native, replace the %% with %%% above.
Supported Features
The ScalaCheck generators and instances can be made available with import jots.testing.*, while the String interpolators and syntax for unsafe verification requires import jots.testing.syntax.*. If you're looking for usage examples, the testing module is heavily used by the library tests.
ScalaCheck Support
The testing module provides ScalaCheck Gen generators and Arbitrary instances. Following are some samples from a few generators. Note private and public keys (PrivateKey and PublicKey) are not generated on the fly, but instead choose from a pre-defined list of keys. This is due to the generation being computationally expensive.
import cats.effect.IO
import jots.JwtBuilder
import jots.SignedJwt
import jots.VerifiedJwt
import jots.testing.*
import org.scalacheck.Arbitrary.arbitrary
// Generate an arbitrary token prior to signing
arbitrary[JwtBuilder].sample
// res0: Option[JwtBuilder] = Some(JwtBuilder(JwtHeader(alg -> "Ed25519",cty -> "䓤鱊鶞ғ赓醑푪嫋圦锍苕\u000f㯟馊頹셻⨳Ē㪛絆ᙜ칚㮐⠅ḝ蘨ᚷ䕓࠶藱줒猸왬蠌倒鲂䡙蘒浜ⱍ턴훝ᷢଚᯜ༕슔㵭苭峤噬Ƕ慂﮲Ⴔ쫁﨟啷ꩠ樘ﰻ鍊暴㥼鎷嚭튥얮ꯥj㟒৬ᆋ펽蠵樓ܼ",kid -> "匕嚐儜蘂柱葉ꚤ蜥ꋯᖥ겛ꬡ⼒ꦻ联Ś㩒鷀袔鹐퓫獩儡ṩ㦹縐Нᚃ鐹疥臼戂ⓢڊ깗চ筬Ӗ圯皫廍鯧Ꭳ躓롎⺍䤒刚蹢హ곽ꥵ柁ኹ御짯鲇ᇷ죸峗빯㛙荡暰ῼ뤜㨀뮮嬿ﷳ㌣缳㚾ᥚ騞昸編㭖",typ -> "JWT"),JwtClaims(iss,sub,aud,exp,nbf,iat)))
// Generate an arbitary token with valid signature
arbitrary[SignedJwt].sample
// res1: Option[SignedJwt] = Some(SignedJwt(SignedJwtHeader(alg -> "ES512",cty -> "骵䭐痍詃⠼終䴊㫉ࠍॺ箺ⵞ⓳臂㦁窑算퓸⣎湞靌ᜇ耏㻙⻂",kid -> "礼쳘㻭㎩⌈I쉝䒣욫斜낏풐ၩ墕찀8您뛜냧폙숱穎䠜ƂIJᶩꋹ֜홬毜䲘괱铄㞨ொ㭮᧭갩ヨ鼙༞ꨖ냡掝␍"),SignedJwtClaims(iss,sub,aud,exp,nbf,iat,jti)))
// Generate an arbitrary token with a verified signature
arbitrary[VerifiedJwt].sample
// res2: Option[VerifiedJwt] = Some(VerifiedJwt(SignedJwtHeader(alg -> "HS512",kid -> "豜ἅ췆腆ᴬ뫭鱗蛿చ俳淚Ӆ௰ㄮ㏮嵕华ꄁ䧳訃鄣恻䂨っ峹퍊陮㐩ἶ퇺⟔텮Ⓓ䏅ヒ犑阚셿닻ऺ赉ɓ집》䶚嬲",typ -> "JWT"),SignedJwtClaims(iss,sub,aud,nbf,iat,jti)))
// Generate an arbitary signing instance using ECDSA
ecdsaJwtSigningGen[IO].sample
// res3: Option[JwtSigning[[A >: Nothing <: Any] => IO[A]]] = Some(jots.JwtSigning$$anon$2@102f5cc7)
// Generate a matching RSA private and public key
rsaKeyPairGen.sample
// res4: Option[Tuple2[PrivateKey, PublicKey]] = Some((PrivateKey(**),PublicKey(-----BEGIN PUBLIC KEY-----
// MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmTZoSk2SCHDBu5S1DjE9
// RDGw6YZwXZFJjG70gHfqjkuQF2DEqo48NWF4Syjgcl78y3pvgLiswqw6CgpoOyEs
// anRnvOSimxZxCNm8x/aRzOSIxq/iRpca9bLbGyv2TXeyBNLJOs6q8zPSwO9wYe3+
// +z+i0aemuenWN4iIBoLzB2lZXGwyn56T+s+HHsHRfCrXNrW0yYmKmhaefXfWrLtj
// kcXfUmMa5xYbysQXaFIUxiebBxmbDiaNw4DgYa2dutgZ1N7+FLpVcPAnCxGVabyJ
// bljpJoATC22Wq3h7fdd2q5AScBjMzxGnpqSqxwAx+4qqTQ32AjgTIMCvyzf+uc+j
// jG4qvZj5UqhRfc/Bedmh70l5kt69MZgzbtqjwOjJQmuqYb0m1Xj60xETxOZvSdG+
// fIeQrEPcwiWsRm8wxSVer31lLnenbYWZRj3FifAJ/wrz0eBLdto6cPVeXkDcKBu0
// eZQwiMoaaGsOMXu4Hdft+RzJC2HQe+vWLwzEPAbauPOcH1TRj/RbTuuaRJW7ZgxS
// 5xFzaNb4GDZdqyr0VGpePf00f258bPSk5FfZihqs1/D1dIEvfodmJO1UniA/XPpc
// vYZAnwKk/gEuHuYrDbtH88YnMSRFWWxQNNavrMrlqrCcUEU+jnqJip/9FwUIjjR/
// eEzqpgzqyimf1UJ9u/ldackCAwEAAQ==
// -----END PUBLIC KEY-----)))
String Interpolators
The jots-crypto module provides syntax for compile-time parsing of public keys (PublicKey). The testing module provides additional syntax for private keys (PrivateKey), secret keys (SecretKey), and tokens (SignedJwt). These are generally sensitive and should not be in source code, except when they are non-secret for testing purposes.
import jots.SignedJwt
import jots.crypto.PrivateKey
import jots.crypto.SecretKey
import jots.testing.syntax.*
val privateKey: PrivateKey =
privateKey"""
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
-----END PRIVATE KEY-----
"""
val secretKey: SecretKey =
secretKey"5BpYD67PafjVoefV11a06MVMGCmr1zoLrFGL019EEuoMtZszHqqpAd6frHFFgGXZ"
val signedJwt: SignedJwt =
signedJwt"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJ1c2VySWQiOiI4ZDNiYmQxNC1kZmQ5LTQ3ZmEtYWFiNC1kNzZkYWYwMGI0ZjEiLCJleHAiOjMzNDUwNjI0MDAsImlhdCI6MTc2NzIyNTYwMH0.8i3xidY8bcAjoBYSKktcyihSdICGXBSBnjp13JYmO_DE5v4_oxY4bSBtZxdoic7OWFKZCcE63I1fFlukzgxVZA"
Unsafe Verification
It is possible to extend the default verifications for custom verifications. The core modules enforces that all instances of VerifiedJwt must have gone through signature verification. For testing purposes, the testing module allows us to create a VerifiedJwt from any SignedJwt without verification.
import jots.testing.syntax.*
signedJwt.toVerifiedUnsafe
// res5: VerifiedJwt = VerifiedJwt(SignedJwtHeader(typ -> "JWT",alg -> "ES256"),SignedJwtClaims(userId,exp,iat))