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 -> "ES256",cty -> "븈ꗥ翈綛澬쓣ꢋ㻣㵷Ɨ璴ᮚ曨цʮ䒢픀牪ᄷᰈ흎☬끯嚠뭭ij懜㙏Գ엠텺桽톭簴杝쁊ⱪ₷䮼屇톕ꔹ囩ᚹ삌㷺웉䢜궑巫쫀槐ꗆ狝䓝ᝆ餭䦮қ伯ᨧ㍚ዒপ耪眎縐ꔝ军ᚎ쮺땭썜▂抇ᶦ",kid -> "醲䤚扭㈰ꨊ⳰Ⱆ蝖쟕㜰〖콊夊ብ抈쵧줭ꛣ跹븈韧㈩흛ᚯ⤪ﭐ퇏㉨쨬䐫ꂑꙩ덝십讦⻦ﷹ",typ -> "JWT"),JwtClaims(iss -> "⮤楔쭵㪹鋷啂莀쫧လႲᱰ⇀쒴ኈ巤䷂潋ം胪૬嚽͆䝭狄放㙺㈉ꂞ壣쑞浟≮旌擠맱䓉癧뎄븯⢛虆ꏔ葳竪磞姑䏹Ⱉ䊳懕্腂븩넅䄡",sub -> "颣モ樁㠬䎽줊ᠶ絷왕㶈ⓛ촥⚝仠䭨ఄ",aud -> "䌔큝▗ḟ桋㽮ꓮ먓ﭽ䳖慢顥ꨗ୮퍜㤧稠慙Õﰑᛏ⅁厰۸⅐콐벛Ǟ錇唍ཫ돶㱣ꄐ溾묞㲯쓘",exp -> 65495216364315045,nbf -> -1,iat -> -9158460546015098335,jti -> "獈湮眎싨趖跑ᭁ쮠胑鑰ሮ打雮⫝뿭뒥췸커䅀蟈暥䰷尭坷퓶࿋ً槶⮕쩕\u0001ﴕ뜃ꇂ葘ꬴ㆑᭺ᐵೕ烺웾㈕햖㍤Ə徰朮睪ᓽ䦀壗핫掛嶠稳錣풃䉎⛚屐ꕫ䰧﮿")))
// Generate an arbitary token with valid signature
arbitrary[SignedJwt].sample
// res1: Option[SignedJwt] = Some(SignedJwt(SignedJwtHeader(alg -> "Ed448",cty -> "ퟩ鏦䒟⺒頲艅㶪缜攤ꨅ\u007f귀崯⊀媂ꉖğ嚌알믮⊈쐿뫆沼术뤰ꍾ옋⊦칤䫫宖槔둳擊찂뻍씂꣙䭌돌灶叉⤡恘켏ᨒ䎊躥泉ᛴ篏㓪厺省瞾媙妰䍳쑂_憁魉즥㤾碉ㆸ֕ༀ괉DZ▝伙㼛涁ꭴ跉옠쓱島勖③啙阳荆ڌ谕",kid -> "㫇뫒猼䤥䘺ᯀ挵匋Ⓤ䑅哋脪꯹鈥騧⸔莇㘋䠤蛱峼∡俇ꌽዶ䱓都䤃섎猶愼ᔵ肝奥ꄮ讋⪎褺䍄䏀幖쉩觤縡夂⋚㾚犱ㅣᵣꊻ栟瀋ﴐⰑ莅ꕁ淴Ǎ屶腰솑걍澚搁Ⱥ콭켥㢁뤏秵颶⡛ࢎ볌쌀訿뷏",typ -> "䪛ྙ㰓孄㸲䴫匸䕵巳Ḋ捓朘쐔ॳʳᔦ굟嶜붽홠轚"),SignedJwtClaims(iss -> "ퟦ羲笍鈀ꝷ벙ꁌ훭ᧆ牺䋮籜旲槁죢贖Ĥ꽦殪㸭ㅫ曽݅僆ጉ⬳饷㷿ᡙῚ熑棶飔徨䇐헲ᛈџꁆૹ렶ꈍꬊ龾犤찣ᯯഇ⏦퇼駹롭ꂶ絻寤벃雩କᬅ詝㿭꒼篅檄콿༾誷䡔쌀집ꌵ㨶",sub -> "풹๏㙻䄛ᇵ絨̤Η鷹改ꅑ䢊黋䑁仈虌㥝趯덠鄞懖ⴊ↋枢큧奿퓳᮶ᦥ窹혂迏鯝쾘",aud -> "騵㠏若埒멁靄㠡墹膌谎麫樒폣讬톃䍠厢퐜髂顩嗦䧉䰔쬑럫ꚼ㼼瀜ꉧ竦枺䙎披㏔狧ꚵ범㼕篇縍썈ꐗꉋ༕և쵃涃ꋮ冯坭妼檃㣭紖돨䔅ϩ螷ԝ꼍ꑻ脡",exp -> -8401598116536069322,iat -> 1,jti -> "₢庞ᬘ裟㡱䉷鈟ⷑ瑊᩿华拾꜀᮷Łᐅ읾騅棣읚䯾폗₈뭌ꔃഥ깂荧畖郃픷潭ⵋ⅗簿©ɡ垪s⧴忐")))
// Generate an arbitrary token with a verified signature
arbitrary[VerifiedJwt].sample
// res2: Option[VerifiedJwt] = Some(VerifiedJwt(SignedJwtHeader(alg -> "Ed448",cty -> "䏗字삁蓄㹦ᐤ溟퀓ꥁ㖽흙瑽ⅴ뤴ᨮ႟䯼ꞩ䞾胹챝崺ྛ긝镮ڢﺆ䯃熶盞毥둽脺蓄旣슝츓譶Ł됉紧ポ髒ⓢꦚ羞낈ᙁ躓팅ꞌ뫞ꏬ딩䚕顬扏읩ጃᳵʣ뛨ᴌद",kid -> "憾䉧頕뢥힟ー閦扏ꬠ픉ꢤ顉⍘틤泎붏띐滜愮䁺ꆲ饨쬚啛穚쩗杛辗迒酔羗鳆嵻藍嗈莴憱⾐㐘걥㡫닫疌雠聥彗频罘勺帧夊⦥ˋⒻ驘䫩ᵜ⯬돝廼稸ﲫ뺐矟칠ꋜ㥠퐚倕옏虎Ⴏ챬쀿觌慳氽ꂖ♲㪦ᆹ縨⇅ﱿ컥",typ -> "쎕ᆆ끣ፂ떸뜾軽䅳綐௶녗㱬랽ܟ捰ऒ鑰놱䭼㐣䅍瀁㜫쵃壭䱓䱬搵푂끸≖툎嶳ꩀ뉦䅚쓞벉柃"),SignedJwtClaims(iss -> "鰀탆突죿짗캞婇턿ާ쏡醿鼤鱴팴㘪懧숉寳鰋ꗱ隓읯캣娤嘵郕緼ṧ鉉쟨乚㣅틚翛瑷篌䰽芦釲ꍊ鍥녝ႪݸƳꜣ瓆䞯含铕Ⅺ꾨ゅ迺▘쨩ꉺ栔",sub -> "鵈᬴洷䨌⯠끺襭ᢆ䚘胤䍿⇯楬鎴췿䃰ᙾ䱻檥ꁔ힟쿪㉒䈺춳꼆䨥롚㖉种渉羡㬩웙ಀ姛촕㡋쉀톙氅䭄ࣈﭔṍ䁺㻱뉝嶫㑌ヨ稼椪좕ô鷃蘫怜⫮",exp -> 1,nbf -> 301470432867918031,iat -> 5236873173418738824,jti -> "肃ꓴ䟿6䡴᪪ﬢ쵟锡㵶╘咩앣侉浃쵤鹖£ᅪ限姻蜤꣭⏭ﱫ겮ꀊ炠⎏ﯺ໖ﺉ㖞픡슆͛㼢怕昣ᦫ늶鴹ញഉ쮱緫䧥ࡅ對Ꝧᯑꇍ㿖搥ᨆϒ형쭹鞚ﯦ廼侉父躕ㅃ饱聁瀙꿈֤鵤ﻥ蝬䊕ڐ놏缪ꁣ쓹孒̇┴췤")))
// Generate an arbitary signing instance using ECDSA
ecdsaJwtSigningGen[IO].sample
// res3: Option[JwtSigning[[A >: Nothing <: Any] => IO[A]]] = Some(jots.JwtSigning$$anon$4@2fdd9917)
// 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))