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.2.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 -> "ES256",cty -> "軤䃾梙痻簁藺χᙨ僐苚☏Ҩꟲ㷔湋璡敁镂̊鉖Ἀ䁔焰ḯᡨ巴尷﹄ၴ↸薂嵚ᴜ쩿訋韄曽갛䫛ꃂ뿪䥡퍷⪷奄ﶳ툆ネ⧡㽴솀㤸넋⟣獻႟㗇恷ᷱ䤱㰡㚻篆ﲌ糭ෙ乁✖䡲詷ẜ敭閼ᚰ唬缅坥㑗秝ﮋ༒䚧辟癸帞棚",kid -> "ⱗ夨꿆謦쾚쁂䈿칋䣮ဘ濋공ⶖ벳㢰䯴셥ⶪ誆쥹썖ᰲ⊊䌬偖쇾ꗺ☺帼ா鸇ﻲ諄盶졜짹춛ᠸ뼃͕쵇ꯂ",typ -> "幭⽇ꇐ㫎ཆ쿣ᥢ₰詫በ뺤ꫣꖛ陳궴붼ᛴ洋"),JwtClaims(iss -> "㛱䁋䲰듰묏 ฺ筵䶋⓸鳑犬蠦塾唼㼚취䇲닃袃纊䘅㳸銹㋫拾藪ѓ띠㾣㔜⢊읃贅წ恼獬䍩릿䜐켯뻨闡䙡쎇Ο刎䒀쑤롼ﶜ좑䭦㌳䑻톘ㄼ氶臑쉼鰔囙涩䆋䜜냺눙橩ꉶ餔寶賌쵦䡬“랼ⳡ",sub -> "鰉巻ᝠ풭ﶊ瘰埬嚪ⲥ줹刭୴抈艒ो籦媔䧟摇烚뮆ﳚ헆봢늪뻽㋟愴篳贈ᑗㄌ娮捎㚛扉荿줟댝濱쵎鑃ɿ껗",aud -> "⺍劙髧ժ퉇႑뾙얻뭳湨月ㅭ뺼贶躺럀࢛≁Ͻ횔兆颭砑㰖䁱ㄔㆪ㝼퇫邺塂퉣Ҳ諁﹙繈ﰻ붥ⓁЕ㉤粺⺵ʏ",exp -> -6594715421884647985,nbf -> 1,iat -> 9223372036854775807,jti -> "釷란離ܔ엜崝ş煨₦ⷍ魴ᓰ뼛ᅢ織ӎ붙⡾┮䩤ॽὛ釖㉣ꧠ䄉봰캔ྛ䢨᭙뀷푐䕝ࡽﭵꉚⱫ")))
// Generate an arbitary token with valid signature
arbitrary[SignedJwt].sample
// res1: Option[SignedJwt] = Some(SignedJwt(SignedJwtHeader(alg -> "HS384",cty -> "㫐㡑ⓞ앓渲꒨",kid -> "挃Ϯ읯䞋┷㈀챆脏貘䶨好퀢ꁙᴑ뱢ླྀ郟垀岢輒Ᾰᶛ䮾䪘小伖혡㞇掣࣋뽻㱓ొ鷞逸㘷䃄挛麛䚝ꎨ欢쪓",typ -> "ꝕ겹馔붶㯜㍢䴳"),SignedJwtClaims(iss -> "注膨얣髂⽌䐊嗀≽",sub -> "쬽滦➙젣멼㛲趱殨铓お愞쬁ࢽ쵏왵疞똲곞纟㽛浟繱ੜ⒥毝ꭏឤ폋뇠ⲩ查Ẹ┄ٕ䆢ᷨꐽ鞍폡쬘Ὓ룧즘퍦毁뗧훳갅칑砼㱶矛ే㤃캅梏倳먯",aud -> "⻚ꛧ䋭䕁䆚3⇔⊅巫㝬邓ᨺ트籿騧쾱鴤값⾺촑ဃ⣳甁樾ᵮዸ䐙鋛꧈㠣娰嶻橽扸ׁ숺ᏽ萸籺%짭寨菰픩\u001f张퉣Ể氣闐",exp -> -9223372036854775808,nbf -> 627082551353850939,iat -> -9223372036854775808,jti -> "铲㣒쟍埔Ԓꦴ䔰㞄몰")))
// Generate an arbitrary token with a verified signature
arbitrary[VerifiedJwt].sample
// res2: Option[VerifiedJwt] = Some(VerifiedJwt(SignedJwtHeader(alg -> "RS512",cty -> "ɕ䷡罃笓罤ᕯ",kid -> "",typ -> "JWT"),SignedJwtClaims(sub -> "⦪᳄黆寗掸⽜ﭣ注纪䄝缩됦㨧䢬琭砖ꅄ뙝矂籠ᵪ㦑꧆㼋醊㰠᭚库倐淭㬌翡俣췻爵度ɢ鿉镐捫䭄鬳⇽쑨젂짬﹐ꦒ",aud -> "⸆㐊龷묐ⱔ⊖斱Ƭ㣶䊙䦄뀮퐰徂",exp -> -2211554968953121187,nbf -> 1,iat -> -1,jti -> "묛䥦䔮嵤異䋻≱ᆊ䙡ꡣ工⭁凄䲛嵆ⷌ䳺俍䄴붵鏵垵磤ἄꫦ㫥ⶍ尦墝덷ᮾ络渎ः쩿䳘ᗳﳌꥁ蒫㕼⁔꭫㔩넦넙撎밸鿵䄑殉즳鎽芆䶔\r値煉遖腿퐺瓫")))
// Generate an arbitary signing instance using ECDSA
ecdsaJwtSigningGen[IO].sample
// res3: Option[JwtSigning[[A >: Nothing <: Any] => IO[A]]] = Some(jots.JwtSigning$$anon$4@557d4d04)
// 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 -> "8d3bbd14-dfd9-47fa-aab4-d76daf00b4f1",exp -> 3345062400,iat -> 1767225600))
VerifiedJwt.fromSignedUnsafe(signedJwt)
// res6: VerifiedJwt = VerifiedJwt(SignedJwtHeader(typ -> "JWT",alg -> "ES256"),SignedJwtClaims(userId -> "8d3bbd14-dfd9-47fa-aab4-d76daf00b4f1",exp -> 3345062400,iat -> 1767225600))