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.1.1" % 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 -> "PS384",cty -> "Ꮯᾛᵅ힂惏ꗦꭠ쁾✿鐱筘䢳ᨛ劭ꔘ嶷漕蕃伨쯝靼䳤暲讕켩垿츮䮥骛ۺ넪밁蜧ⴙ欼涑팀谿䊩㝿殫㚽뮗帿㋥筬錢ꊀꗺ",kid -> "墈滔憡䞇蛙礪ᠯ缾特ቛ뤂캞囫樚诽ꢉ⇕漀翍㳳殙莤ꕀ㡹僆䰈ꍇㇰ䚘",typ -> "劷顔ፅꀯ넟祽ㄛช鬅睠嵬혿낉噫앸⢃悊狝튢櫗뻜叕胺呭戄ﳔ묖玉뻿꒬鷱쉬篡蟲稟宷펭暗첖擑磗菗豱䟐忘ả䂊쮛폠ᇏ歍봻瘾괘윌貨큾拺˙蹏囻낫䯰듬栠腙䊊遙뜱ᱬ"),JwtClaims(iss -> "쨵Ὓ顀ᾊ㎴ﱙ逽둭ꐡ具핏꒵䴨ඇ伄廿輓Ꮧ趡╩挶퐀봯٦赫矤퇖堻ﲀ嫯宛泏土ᐔ懚鍐䴯韽蠈ፍ䔊鹽얳䳸郂ՠȟ呚腓䮈ᇽ䍶⏆겜柤櫹ᖜ㾿䳳◙훴秃씂벛뗦ꦉ▸",sub -> "搊焮瀞떬䰃ً韡쀈⭚㺷晸ၔ̪䣑엦㙨蒟垄窟!ꜘᑪ茴嘪㧡琢Ꜥ䍴맿䩏㍳⨒嘻ရ☆轅쫻⡶攑ㇺ聥焥ぢ銩ܶ偻♚ᚑ蛵嶜딣ﯩ낅ᦁḞ샹㵄궴뙅현稑礖ꙵ瞠ᜩ茤",aud -> "慠䛉퓙痢玧짡丹䬋䘸괱颅띥䅙ᔒꐓ㕒䮱謸몆촧〜낄괯햻립쀄夏愯}㯆暢⍙䞒ᆠ跟醢擯쑠쮎嗳ᙎ⚂䟦ೋ꣹㟢렪굃ⷹᕬ䖅䁫经턼헧涩賋睿䱂胜멍",exp -> 4330739210070933070,nbf -> -6828065899690616343,iat -> -1,jti -> "륐ᇠ璘푢馟֖䮨⇛떍岺퉗탙ꇟ䝇橰㈘叴弲퓇幵桺绑璞﹀錒諰咜䒌仦頳鋬䄻\u0085壟楽냃畦壌载搞哰Ŀ")))
// Generate an arbitary token with valid signature
arbitrary[SignedJwt].sample
// res1: Option[SignedJwt] = Some(SignedJwt(SignedJwtHeader(alg -> "Ed448",cty -> "룋㤇曐䔾檡패ࢃ陈ꝱ걽",kid -> "枢ࠄᭃ丆㫟龌鵊困䇍辘棘㈳𧻓嘸괱낙鎢캋䁸❨マ齃쌸줯퍆泥鏥䅗⌰앙꺶㹙✲䰳㭈웹㇈ᮕM㧗⮔犃掵ﭹ拹",typ -> "᭹剻蝍⪪ᢵ㝟粑埚ㄩ顭킪榬ӱℾ熏ꞗ٧戞넭뛲㉟펠ۀ麈䅵镑ﺟ뱻ᮽ萕妜䍈鱎퍰嵾"),SignedJwtClaims(iss -> "ఞ厛棯쇱ꩂ嗜",sub -> "⩉≑ÆᏡ䛥쮬윫许Ꮃ훨쑋蕓㚐ɓ햖쓠≣螔䫛䄚櫹韱⧴㶾鎥팹頻浰䞒♔溝둡恋ᐅ嘈ᰊ梼䚀䰇࿎紒폗ﴷ㢿빂룶麵坢ﰨ鳰줇鲽㷏望뛔T뀊缘긹胁㖷",nbf -> 1,iat -> 1,jti -> "鄒㋛⒃녉닱쇩솠룯㠒")))
// Generate an arbitrary token with a verified signature
arbitrary[VerifiedJwt].sample
// res2: Option[VerifiedJwt] = Some(VerifiedJwt(SignedJwtHeader(alg -> "HS384",cty -> "٣薰餥窒沗㘆ʃꦧ띥팳蜰龪蜮䧕ﱬ풫꣧行䯟ᝁ厺훨謘뢛﨑䡚㿳ᮨ숻",kid -> "럥볩⠘貚짮奾䂨᷃蚙䷼熭쇮릆ハᶔ톼阬褠ᤘ趆냤索㢋糾鐥츄뙵ᐷ畑檬骏ጺ",typ -> "JWT"),SignedJwtClaims(iss -> "걬䍆鵘攂喱ꏧ聭ﻁ얆驮귺",sub -> "螟鈠趴콫䛙ꮋธ륢ﳟ㯯Ľ슡虲궓$㼃섓噡簦ᒭﻎ乕폰깨襀ࡱ킩ົ껆呾鿴╡뾢렧엀쩷脵䭅㨆ꕰ쨧풠䃼痫揈䄐ʎ㗕軌悮̭渤迪ᘧ稭牞꒮뺫䍾䂹❛㪹碚福愃魏搸뗹鋗㰷喻ࣣ聗濔",nbf -> -6088673718062282645,iat -> 9223372036854775807,jti -> "Ȗ憋䴸蘧訜焯寡脋腱")))
// Generate an arbitary signing instance using ECDSA
ecdsaJwtSigningGen[IO].sample
// res3: Option[JwtSigning[[A >: Nothing <: Any] => IO[A]]] = Some(jots.JwtSigning$$anon$4@3486d884)
// Generate a matching RSA private and public key
rsaKeyPairGen.sample
// res4: Option[Tuple2[PrivateKey, PublicKey]] = Some((PrivateKey(**),PublicKey(-----BEGIN PUBLIC KEY-----
// MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn51WIz3xwJztQxQE203/
// KYWHEDpASmT3QIW25h3yU+ZNgA3tm661QCpjiD/OoaEnGmJjVBgvqu5n3cpm2rI0
// YiijvbrOTskeAJ9jt79iab0oxlfrd9B9u9lubTbpyzmJ3I0+SyFU22efNASf201D
// exVonPRCNX66n93363bLvMHjRjHj+yCrOATRuJdq1Neeexm7/ZVP1jXa8+Hl52nF
// prymh31MocK2jXMxYNQvpxBcWz0GviPkQDMu/gEFVvIzMjBHbtQy9od+sToXi8y4
// Wb2f6hzDTud4SxQa+YAaDCuQ2hiosU1pOwe6uaoqTq3JI8/XT0FNQBDkVuF/d6Pt
// 2wIDAQAB
// -----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))