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.2.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 -> "PS384",cty -> "Ⱦ쮅Ῑ䖸",typ -> "럈拝旑갓ɹ뢋﯀᝺䱅狇灁"),JwtClaims(iss,sub,aud,exp,nbf,iat,jti)))

// Generate an arbitary token with valid signature
arbitrary[SignedJwt].sample
// res1: Option[SignedJwt] = Some(SignedJwt(SignedJwtHeader(alg -> "PS384",cty -> "諺׮콦㬡ꗻ왏뽾욊췧헰딹୕瘈渪꡺䝖祼恘嗫犒㚻랑遼켹䫑쮨ꥇ諎躲ᜩﰹﭬ蔂ܸ扭찕봩ἣ똷僨淋ᄿⷄ状툱鍆ꚗᔏ끔턳䘤-ੴ鳩R綕窮쀙ꂟ䐛咼4獸ᗹꗦ䄂葃掩籌",kid -> "洨䁻뒹嚊掓陖㴾媄⏸饐糶ꙉ㙮鵐텩㭧꿎鯎嗘狪ఋ鐇놽剗Α靳巎躅ⓤᬺ៑枂銂宲⡖펊㽲嗰ᡢ⬈㫌ṻ螗佉瀊떝闳版副醭⯱柊㑅碆竐쌐禊져鉈ᴌ檲懓媞欳⊜ᗕ긟뾌䴰鍱緎鱞㐩ꇍ䧩",typ -> "ᙟ갩䵮ᑄ퀻懊捷襳ꆯ덐ꮟ"),SignedJwtClaims(iss,sub,exp,nbf,jti)))

// Generate an arbitrary token with a verified signature
arbitrary[VerifiedJwt].sample
// res2: Option[VerifiedJwt] = Some(VerifiedJwt(SignedJwtHeader(alg -> "Ed448",kid -> "",typ -> "ꝶ싰贺죟戉녴焥᷂禕骂皂끴ꇼ戌飮ᵛ୩練籷斻า䎫傏⪰毳颼ᣔ"),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$4@3cdd2617)

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

VerifiedJwt.fromSignedUnsafe(signedJwt)
// res6: VerifiedJwt = VerifiedJwt(SignedJwtHeader(typ -> "JWT",alg -> "ES256"),SignedJwtClaims(userId,exp,iat))