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(cty -> "ꊇ惥뫕鈩摹ŀᗝ☊ꘓ笗尟䌈ꓳㆡ钶ꞩ蛸蠬睗ⴧ椖䁲ཁꄢ눱᳣㢲䏹ി⨹䉐埸",kid -> "昼쎦㍌汃꫔碑眶Ƶ먀瑡焹授৙叩鮃昀닔⃧栲᩷婪㮚掠槾授⯈∔ᘈ듺亶Ϟ랑ჿ幺ֆ婗쓁얪ᚘ쵺ă忴䠥蟳⏇쀉鳖晼㗗㷲谴ྲ샙ᱬ䶏⧵윇ᚚ탘휨꫊틢굼뒓㟚葷쮤艣쁇Ⓜ鐫⡂좔끈炣朌禣悦ጫ㥱颙霺ꏙꤶ梑㬆",typ -> "長ϙ랱鞜⁌涅꽖섴﯂憢벺怒ꌁ햪젡ࠌ䝵븝蠹檌ꉪ뛛韑ꄧ覣藆던ښ셙䩢⒪㯝⓳ž㋤塆쏹淮鴭"),JwtClaims(iss -> "贔佁瞚泏邑꩏쀦媘쎩헕셛ﶒ惽ꈿ軡쫤眣Đ帠らꅐ녇쩿晣杞舏핿多䉀贆挕䩊닓ꚾꩈ劯쟢᏾딑虤㓿䯂豩⪇孿茎뺕罃蔶䶄눏㼷강瞫伐觙﷑쩔乸",sub -> "偎㙓虖令佥藰輤ꡃ䤖壌∀ᷣ䡱饁滜ﵝ틪钻衄흺ᢋ緌츢廞ﷲﲎ蟧ₐሠ拌⫙냖㠀컯ᔚ炝嫸폆薇氞뮎㤰⮳㭆稭媩댬툶崚䯭綴䁤庿韵>䎳流귀㣎鏿澓뷌杄ﱿ鷪峡葄\u0001楊ﻼ乷ఠⰪ읩갉",aud -> "ॗׯ齫謴ꞻ찣覝➵咙⇽乬繁⛫噤莦狕䥽ᒤ⃩瑉赻",exp -> 8701681405491018314,nbf -> 3861238413294600250,iat -> -1,jti -> "鷊왐詝\u001e岜墟闯⼵")))

// Generate an arbitary token with valid signature
arbitrary[SignedJwt].sample
// res1: Option[SignedJwt] = Some(SignedJwt(SignedJwtHeader(alg -> "RS512",cty -> "圻暷킕갿葊闳蚵ꑜ뼈ើἝ㚒⩱顸晴⫉㞷﷝ᓠ㦳可ᣦᒏ᥷䋚潢駮巡␹⢆᫄댼㸠⍮岽",kid -> "嫿줰퐘⛐啁㚽䌃ᕑஒ뙍鿏鋲玔נּꧧ䉆폒躃ᤅ淸넃ꜳᲇ鄕짢謮ꑠ",typ -> "莺뼋⣕骾᪒ꃂ瞽ﭶ♀泟₀㳨豐㧕꽀ꮥ⛢⦄쩓᪇⪽䩍楂囩戒沒岋䮪漠淚"),SignedJwtClaims(iss -> "ඝﯔ困ᓯᬳ⍏盁䲰∇뇴宍㼿崍薵磾☙⫪ᛆ〖ﴙ㇆ㄚ큡ᝣ贼睋鮰䰳࿣⚃뺽툜㘫歂昈ꦲሃ䄏멆놶ꨙ៊",sub -> "幀迬㶾埉ˏ巧볁줱᜹憻䞘ּߖ빵把䑞ꦑ뺸䆯ﺰጪ㵩벨ꦵ",aud -> "韉쒒㌑邽㑣푤蹙塆닯ﲦ乃⑄댤᛿쉕ꆦ钞꺲ꋠﱌ멢؝ॏ厊㧐툘寄⥆ঁꟷ䎓垔ム玆╦풻㖱ᔭ冰硇ヾﺯ㯜债攧⠅ܻ⫼䲜왦꿜ྷ㛫䓊",exp -> 2858791750670280376,nbf -> -2489209374106646281,iat -> -9223372036854775808,jti -> "㗷⵵段頏쉧ﱿㄦ贚㵥扪噚鎱߻斄꣊㻠흼껑䦴あ")))

// Generate an arbitrary token with a verified signature
arbitrary[VerifiedJwt].sample
// res2: Option[VerifiedJwt] = Some(VerifiedJwt(SignedJwtHeader(alg -> "ES256",cty -> "싁᷽夘ሪ胳㻗ᴿᩩ궕㊜阠싟妕ꔎ竔嫥楿㘺큓㼻⏯罁퍪▷걚ᚯꯒ믻ᰰ캙ᰇ틡᡽ᚪ뢣쾐ၨ黧鵥䘎ʁ凱⾑萤屧鏲榧볠ᝨ敻䞼⏿잶ፂ厾‛赬",kid -> "ꄱ萁齓욵",typ -> "JWT"),SignedJwtClaims(sub -> "ᇦ箩煅⩩煌ᄁ쎽咛ۦၐ騵ꢅ於肎窷",exp -> -9223372036854775808,nbf -> 1365915364670676038,jti -> "閪㪜孼₎컝퍗泬✻Ꚗ麴홢돲뜲䬋ﺟ︲ⶑ燡鹆效ᄴ禚㝄쑂஁졞揋⃜缛䎱ꩯ樂柅壥䔙八ⷘ⹧끫\u0012ꧯή⥬㷎脃䎘ꚦ벡獏⾁Ḙ驴컅캲")))

// Generate an arbitary signing instance using ECDSA
ecdsaJwtSigningGen[IO].sample
// res3: Option[JwtSigning[[A >: Nothing <: Any] => IO[A]]] = Some(jots.JwtSigning$$anon$4@6450b6a8)

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