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.4.0" % 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 -> "㐲嚾髕飱╪㕩꼂饏咾䅧㥁垉椊徖ꇾꎒꂒ힜➎혥祁᧽䴲飃ᳯ⦛↱봅홯ᇊ᯦샫좻춼ﶌ㮾殳㘖鸴阭祿悗⩡ᛢ当꺭济翗맆䱼墺╽鈜",kid -> "눔擴뀁頦្硧Һ䉓鵝㪚̠䁫湼뻈尗鸸눣徵槞ٚ엽쫰⻔렂⯢냄┫㝅ꟼᢗ列穌㷨䍍빕宪뉘뺲뵸褴衃",typ -> "JWT"),JwtClaims(iss,sub,aud,nbf,iat,jti)))
// Generate an arbitary token with valid signature
arbitrary[SignedJwt].sample
// res1: Option[SignedJwt] = Some(SignedJwt(SignedJwtHeader(alg -> "ES512",cty -> "朂셴טּ뭂寜ㆎ蠳⯒õ行쿜㞌楤ⰸ蠆䈍輅㿄㰗섈㿻넺䦒첤鱗㢐뉠᭢༏䠄䕣ᐣ舤豒圠⃒迵餅нఒ㠂逛ﯻ犽⼿迣㣬萐ⷒ啗⾴㷧⮝圾隸瘚跐Ⅿ컅㘺유驴獬펓勝ࣗ먲꩝螂蒚飿ήㆲ跋Ȑ缬㨄圵饠",kid -> "H力ᫍ蘒普䚁ᳮ䨏쉺圹猘了氕䛹냫鐊",typ -> "漎茯漼闽탣暌廠䨝陘읙ௗ萂鮕ᚰ앫忣䵲ꏗ❂득슪亍䡤皧䆺뛣⡴ᕷ獢䟈贷懻뜓ᤴ뵩暦ȫ穔꺞㆐ڤ"),SignedJwtClaims(iss,sub,aud,exp,nbf,iat,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,aud,exp,nbf,iat,jti)))
// Generate an arbitary signing instance using ECDSA
ecdsaJwtSigningGen[IO].sample
// res3: Option[JwtSigning[[A >: Nothing <: Any] => IO[A]]] = Some(jots.JwtSigning$$anon$2@4fa1b468)
// Generate a matching RSA private and public key
rsaKeyPairGen.sample
// res4: Option[Tuple2[PrivateKey, PublicKey]] = Some((PrivateKey(**),PublicKey(-----BEGIN PUBLIC KEY-----
// MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwUC8SFKv+YdGfrkeZiJn
// n9Oqdk0H37ZhWf5xOe1aAI+LLz9SWr+WAY+MSZyJifQDUMC+gvoYNTXMl8lrW8gY
// Z6cf6/C9qTR6sKwYl/+LtEuubzkjQn8CTdwKu8IUxWlscdZLLBnHdi7/np9UkBcl
// q7DI98ModCMz7ojl07ss2ZRHbpDKjhbdIpc959+SQIU6en5PKrVsG9zFj6Wg046x
// qksl1a1LHkX8V872Bty0VhsTxrOaQtsHNklXOb8DPHcJ0cXe961YbA48y7R0e+9h
// r5bfTPRO72LFxy6T+a8NrBr4Y376q5tLuSqRuRpU63Lc864P074WmwSD0d/Y/kHI
// ZwIDAQAB
// -----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,exp,iat))