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))