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