JWT Core

Go back home

Jwt object

Basic usage

scala> import java.time.Clock
import java.time.Clock

scala> import pdi.jwt.{Jwt, JwtAlgorithm, JwtHeader, JwtClaim, JwtOptions}
import pdi.jwt.{Jwt, JwtAlgorithm, JwtHeader, JwtClaim, JwtOptions}

scala> implicit val clock: Clock = Clock.systemUTC
clock: java.time.Clock = SystemClock[Z]

scala> val token = Jwt.encode("""{"user":1}""", "secretKey", JwtAlgorithm.HS256)
token: String = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoxfQ.oG3iKnAvj_OKCv0tchT90sv2IFVeaREgvJmwgRcXfkI

scala> Jwt.decodeRawAll(token, "secretKey", Seq(JwtAlgorithm.HS256))
res0: scala.util.Try[(String, String, String)] = Success(({"typ":"JWT","alg":"HS256"},{"user":1},oG3iKnAvj_OKCv0tchT90sv2IFVeaREgvJmwgRcXfkI))

scala> Jwt.decodeRawAll(token, "wrongKey", Seq(JwtAlgorithm.HS256))
res1: scala.util.Try[(String, String, String)] = Failure(pdi.jwt.exceptions.JwtValidationException: Invalid signature for this token or wrong algorithm.)

Encoding

scala> // Encode from string, header automatically generated
     | Jwt.encode("""{"user":1}""", "secretKey", JwtAlgorithm.HS384)
res3: String = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJ1c2VyIjoxfQ.Do0PQWccbp1J7ZWcFL-_IY9OFaI-7t75k7-NxZ52jk2kAb0sFopJEeZapkiXthEp

scala> // Encode from case class, header automatically generated
     | // Set that the token has been issued now and expires in 10 seconds
     | Jwt.encode(JwtClaim({"""{"user":1}"""}).issuedNow.expiresIn(10), "secretKey", JwtAlgorithm.HS512)
res6: String = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHAiOjE1NjE5MTMxODEsImlhdCI6MTU2MTkxMzE3MSwidXNlciI6MX0.kHK2b6yWYmCdKsb6eVMERoPq4jCAnjzN9xhBNhhMe-p7nRVKQVQLqa92zPnRLFLc2nUsrZHFX40W7DxPbXgLZw

scala> // You can encode without signing it
     | Jwt.encode("""{"user":1}""")
res8: String = eyJhbGciOiJub25lIn0.eyJ1c2VyIjoxfQ.

scala> // You can specify a string header but also need to specify the algorithm just to be sure
     | // This is not really typesafe, so please use it with care
     | Jwt.encode("""{"typ":"JWT","alg":"HS256"}""", """{"user":1}""", "key", JwtAlgorithm.HS256)
res11: String = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoxfQ.kaxGIncoYdxOD5RxfwwiP7mRxqUnRqDemW_f9R1k98U

scala> // If using a case class header, no need to repeat the algorithm
     | // This is way better than the previous one
     | Jwt.encode(JwtHeader(JwtAlgorithm.HS256), JwtClaim("""{"user":1}"""), "key")
res14: String = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoxfQ.kaxGIncoYdxOD5RxfwwiP7mRxqUnRqDemW_f9R1k98U

Decoding

In JWT Scala, espcially when using raw strings which are not typesafe at all, there are a lot of possible errors. This is why nearly all decode functions will return a Try rather than directly the expected result. In case of failure, the wrapped exception should tell you what went wrong.

Take note that nearly all decoding methods (including those from helper libs) support either a String key, or a PrivateKey with a Hmac algorithm or a PublicKey with a RSA or ECDSA algorithm.

scala> // Decode all parts of the token as string
     | Jwt.decodeRawAll(token, "secretKey", JwtAlgorithm.allHmac)
res16: scala.util.Try[(String, String, String)] = Success(({"typ":"JWT","alg":"HS256"},{"user":1},oG3iKnAvj_OKCv0tchT90sv2IFVeaREgvJmwgRcXfkI))

scala> // Decode only the claim as a string
     | Jwt.decodeRaw(token, "secretKey", Seq(JwtAlgorithm.HS256))
res18: scala.util.Try[String] = Success({"user":1})

scala> // Decode all parts and cast them as a better type if possible.
     | // Since the implementation in JWT Core only use string, it is the same as decodeRawAll
     | // But check the result in JWT Play JSON to see the difference
     | Jwt.decodeAll(token, "secretKey", Seq(JwtAlgorithm.HS256))
res22: scala.util.Try[(pdi.jwt.JwtHeader, pdi.jwt.JwtClaim, String)] = Success((pdi.jwt.JwtHeader@ac020068,pdi.jwt.JwtClaim@a0b74282,oG3iKnAvj_OKCv0tchT90sv2IFVeaREgvJmwgRcXfkI))

scala> // Same as before, but only the claim
     | // (you should start to see a pattern in the naming convention of the functions)
     | Jwt.decode(token, "secretKey", Seq(JwtAlgorithm.HS256))
res25: scala.util.Try[pdi.jwt.JwtClaim] = Success(pdi.jwt.JwtClaim@a0b74282)

scala> // Failure because the token is not a token at all
     | Jwt.decode("Hey there!")
res27: scala.util.Try[pdi.jwt.JwtClaim] = Failure(pdi.jwt.exceptions.JwtLengthException: Expected token [Hey there!] to be composed of 2 or 3 parts separated by dots.)

scala> // Failure if not Base64 encoded
     | Jwt.decode("a.b.c")
res29: scala.util.Try[pdi.jwt.JwtClaim] = Failure(java.lang.IllegalArgumentException: Input byte[] should at least have 2 bytes for base64 bytes)

scala> // Failure in case we use the wrong key
     | Jwt.decode(token, "wrongKey", Seq(JwtAlgorithm.HS256))
res31: scala.util.Try[pdi.jwt.JwtClaim] = Failure(pdi.jwt.exceptions.JwtValidationException: Invalid signature for this token or wrong algorithm.)

scala> // Failure if the token only starts in 5 seconds
     | Jwt.decode(Jwt.encode(JwtClaim().startsIn(5)))
res33: scala.util.Try[pdi.jwt.JwtClaim] = Failure(pdi.jwt.exceptions.JwtNotBeforeException: The token will only be valid after 2019-06-30T16:46:17Z)

Validating

If you only want to check if a token is valid without decoding it. You have two options: validate functions that will throw the exceptions we saw in the decoding section, so you know what went wrong, or isValid functions that will return a boolean in case you don’t care about the actual error and don’t want to bother with catching exception.

scala> // All good
     | Jwt.validate(token, "secretKey", Seq(JwtAlgorithm.HS256))

scala> Jwt.isValid(token, "secretKey", Seq(JwtAlgorithm.HS256))
res36: Boolean = true

scala> // Wrong key here
     | Jwt.validate(token, "wrongKey", Seq(JwtAlgorithm.HS256))
pdi.jwt.exceptions.JwtValidationException: Invalid signature for this token or wrong algorithm.
  at pdi.jwt.JwtCore.validate(Jwt.scala:606)
  at pdi.jwt.JwtCore.validate$(Jwt.scala:589)
  at pdi.jwt.Jwt$.validate(JwtPureScala.scala:19)
  at pdi.jwt.JwtCore.validate(Jwt.scala:614)
  at pdi.jwt.JwtCore.validate$(Jwt.scala:613)
  at pdi.jwt.Jwt$.validate(JwtPureScala.scala:19)
  at pdi.jwt.JwtCore.validate(Jwt.scala:673)
  at pdi.jwt.JwtCore.validate$(Jwt.scala:671)
  at pdi.jwt.Jwt$.validate(JwtPureScala.scala:19)
  at pdi.jwt.JwtCore.validate(Jwt.scala:676)
  at pdi.jwt.JwtCore.validate$(Jwt.scala:676)
  at pdi.jwt.Jwt$.validate(JwtPureScala.scala:19)
  ... 252 elided

scala> Jwt.isValid(token, "wrongKey", Seq(JwtAlgorithm.HS256))
res39: Boolean = false

scala> // No key for unsigned token => ok
     | Jwt.validate(Jwt.encode("{}"))

scala> Jwt.isValid(Jwt.encode("{}"))
res42: Boolean = true

scala> // No key while the token is actually signed => wrong
     | Jwt.validate(token)
pdi.jwt.exceptions.JwtNonEmptySignatureException: Non-empty signature found inside the token while trying to verify without a key.
  at pdi.jwt.JwtCore.validate(Jwt.scala:580)
  at pdi.jwt.JwtCore.validate$(Jwt.scala:578)
  at pdi.jwt.Jwt$.validate(JwtPureScala.scala:19)
  at pdi.jwt.JwtCore.validate(Jwt.scala:655)
  at pdi.jwt.JwtCore.validate$(Jwt.scala:653)
  at pdi.jwt.Jwt$.validate(JwtPureScala.scala:19)
  at pdi.jwt.JwtCore.validate(Jwt.scala:658)
  at pdi.jwt.JwtCore.validate$(Jwt.scala:658)
  at pdi.jwt.Jwt$.validate(JwtPureScala.scala:19)
  ... 268 elided

scala> Jwt.isValid(token)
res45: Boolean = false

scala> // The token hasn't started yet!
     | Jwt.validate(Jwt.encode(JwtClaim().startsIn(5)))
pdi.jwt.exceptions.JwtNotBeforeException: The token will only be valid after 2019-06-30T16:46:17Z
  at pdi.jwt.JwtTime$.validateNowIsBetween(JwtTime.scala:57)
  at pdi.jwt.JwtTime$.validateNowIsBetweenSeconds(JwtTime.scala:73)
  at pdi.jwt.JwtCore.validateTiming(Jwt.scala:561)
  at pdi.jwt.JwtCore.validateTiming$(Jwt.scala:554)
  at pdi.jwt.Jwt$.validateTiming(JwtPureScala.scala:19)
  at pdi.jwt.JwtCore.validate(Jwt.scala:585)
  at pdi.jwt.JwtCore.validate$(Jwt.scala:578)
  at pdi.jwt.Jwt$.validate(JwtPureScala.scala:19)
  at pdi.jwt.JwtCore.validate(Jwt.scala:655)
  at pdi.jwt.JwtCore.validate$(Jwt.scala:653)
  at pdi.jwt.Jwt$.validate(JwtPureScala.scala:19)
  at pdi.jwt.JwtCore.validate(Jwt.scala:658)
  at pdi.jwt.JwtCore.validate$(Jwt.scala:658)
  at pdi.jwt.Jwt$.validate(JwtPureScala.scala:19)
  ... 276 elided

scala> Jwt.isValid(Jwt.encode(JwtClaim().startsIn(5)))
res48: Boolean = false

scala> // This is no token
     | Jwt.validate("a.b.c")
java.lang.IllegalArgumentException: Input byte[] should at least have 2 bytes for base64 bytes
  at java.base/java.util.Base64$Decoder.outLength(Base64.java:668)
  at java.base/java.util.Base64$Decoder.decode(Base64.java:534)
  at pdi.jwt.JwtBase64$.decode(JwtBase64.scala:9)
  at pdi.jwt.JwtBase64$.decodeString(JwtBase64.scala:17)
  at pdi.jwt.JwtBase64$.decodeString(JwtBase64.scala:20)
  at pdi.jwt.JwtCore.splitToken(Jwt.scala:193)
  at pdi.jwt.JwtCore.validate(Jwt.scala:654)
  at pdi.jwt.JwtCore.validate$(Jwt.scala:653)
  at pdi.jwt.Jwt$.validate(JwtPureScala.scala:19)
  at pdi.jwt.JwtCore.validate(Jwt.scala:658)
  at pdi.jwt.JwtCore.validate$(Jwt.scala:658)
  at pdi.jwt.Jwt$.validate(JwtPureScala.scala:19)
  ... 284 elided

scala> Jwt.isValid("a.b.c")
res51: Boolean = false

Options

All validating and decoding methods support a final optional argument as a JwtOptions which allow you to disable validation checks. This is useful if you need to access data from an expired token for example. You can disable expiration, notBefore and signature checks. Be warned that if you disable the last one, you have no guarantee that the user didn’t change the content of the token.

scala> val expiredToken = Jwt.encode(JwtClaim().by("me").expiresIn(-1));
expiredToken: String = eyJhbGciOiJub25lIn0.eyJpc3MiOiJtZSIsImV4cCI6MTU2MTkxMzE3Mn0.

scala> // Fail since the token is expired
     | Jwt.isValid(expiredToken)
res53: Boolean = false

scala> Jwt.decode(expiredToken)
res54: scala.util.Try[pdi.jwt.JwtClaim] = Failure(pdi.jwt.exceptions.JwtExpirationException: The token is expired since 2019-06-30T16:46:12Z)

scala> // Let's disable expiration check
     | Jwt.isValid(expiredToken, JwtOptions(expiration = false))
res56: Boolean = true

scala> Jwt.decode(expiredToken, JwtOptions(expiration = false))
res57: scala.util.Try[pdi.jwt.JwtClaim] = Success(pdi.jwt.JwtClaim@3fbb508e)

You can also specify a leeway, in seconds, to account for clock skew.

scala> // Allow 30sec leeway
     | Jwt.isValid(expiredToken, JwtOptions(leeway = 30))
res59: Boolean = true

scala> Jwt.decode(expiredToken, JwtOptions(leeway = 30))
res60: scala.util.Try[pdi.jwt.JwtClaim] = Success(pdi.jwt.JwtClaim@3fbb508e)

JwtHeader Case Class

scala> import pdi.jwt.{JwtHeader, JwtAlgorithm}
import pdi.jwt.{JwtHeader, JwtAlgorithm}

scala> JwtHeader()
res0: pdi.jwt.JwtHeader = pdi.jwt.JwtHeader@71da1600

scala> JwtHeader(JwtAlgorithm.HS256)
res1: pdi.jwt.JwtHeader = pdi.jwt.JwtHeader@ac020068

scala> JwtHeader(JwtAlgorithm.HS256, "JWT")
res2: pdi.jwt.JwtHeader = pdi.jwt.JwtHeader@ac020068

scala> // You can stringify it to JSON
     | JwtHeader(JwtAlgorithm.HS256, "JWT").toJson
res4: String = {"typ":"JWT","alg":"HS256"}

scala> // You can assign the default type (but it would have be done automatically anyway)
     | JwtHeader(JwtAlgorithm.HS256).withType
res6: pdi.jwt.JwtHeader = pdi.jwt.JwtHeader@ac020068

JwtClaim Class

scala> import java.time.Clock
import java.time.Clock

scala> import pdi.jwt.JwtClaim
import pdi.jwt.JwtClaim

scala> JwtClaim()
res0: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@97c880b6

scala> implicit val clock: Clock = Clock.systemUTC
clock: java.time.Clock = SystemClock[Z]

scala> // Specify the content as JSON string
     | // (don't use var in your code if possible, this is just to ease the sample)
     | var claim = JwtClaim("""{"user":1}""")
claim: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@a0b74282

scala> // Append new content
     | claim = claim + """{"key1":"value1"}"""
claim: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@85390be2

scala> claim = claim + ("key2", true)
claim: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@102a1db1

scala> claim = claim ++ (("key3", 3), ("key4", Seq(1, 2)), ("key5", ("key5.1", "Subkey")))
claim: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@c18a9898

scala> // Stringify as JSON
     | claim.toJson
res5: String = {"user":1,"key1":"value1","key2":true,"key3":3,"key4":[1,2],"key5":{"key5.1":"Subkey"}}

scala> // Manipulate basic attributes
     | // Set the issuer
     | claim = claim.by("Me")
claim: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@b2b0c595

scala> // Set the audience
     | claim = claim.to("You")
claim: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@3e1a2d74

scala> // Set the subject
     | claim = claim.about("Something")
claim: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@b6c24fa3

scala> // Set the id
     | claim = claim.withId("42")
claim: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@b5d231f8

scala> // Set the expiration
     | // In 10 seconds from now
     | claim = claim.expiresIn(5)
claim: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@b637e63f

scala> // At a specific timestamp (in seconds)
     | claim.expiresAt(1431520421)
res14: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@c7898aca

scala> // Right now! (the token is directly invalid...)
     | claim.expiresNow
res16: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@af102826

scala> // Set the beginning of the token (aka the "not before" attribute)
     | // 5 seconds ago
     | claim.startsIn(-5)
res19: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@b28902d6

scala> // At a specific timestamp (in seconds)
     | claim.startsAt(1431520421)
res21: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@32a92b2d

scala> // Right now!
     | claim = claim.startsNow
claim: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@636b7251

scala> // Set the date when the token was created
     | // (you should always use claim.issuedNow, but I let you do otherwise if needed)
     | // 5 seconds ago
     | claim.issuedIn(-5)
res26: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@52c8e6da

scala> // At a specific timestamp (in seconds)
     | claim.issuedAt(1431520421)
res28: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@4666d7a3

scala> // Right now!
     | claim = claim.issuedNow
claim: pdi.jwt.JwtClaim = pdi.jwt.JwtClaim@166d1bff

scala> // We can test if the claim is valid => testing if the current time is between "not before" and "expiration"
     | claim.isValid
res31: Boolean = true

scala> // Also test the issuer and audience
     | claim.isValid("Me", "You")
res33: Boolean = true

scala> // Let's stringify the final version
     | claim.toJson
res35: String = {"iss":"Me","sub":"Something","aud":"You","exp":1561913191,"nbf":1561913187,"iat":1561913187,"jti":"42","user":1,"key1":"value1","key2":true,"key3":3,"key4":[1,2],"key5":{"key5.1":"Subkey"}}