All Downloads are FREE. Search and download functionalities are using the official Maven repository.

.jwt-core_2.13.5.0.0.source-code.Jwt.scala Maven / Gradle / Ivy

The newest version!
package pdi.jwt

import scala.util.Try
import javax.crypto.SecretKey
import java.security.{Key, PrivateKey, PublicKey}
import java.time.Clock

import pdi.jwt.algorithms._
import pdi.jwt.exceptions._

/** Provide the main logic around Base64 encoding / decoding and signature using the correct algorithm.
  * '''H''' and '''C''' types are respesctively the header type and the claim type. For the core project,
  * they will be String but you are free to extend this trait using other types like
  * JsObject or anything else.
  *
  * Please, check implementations, like [[Jwt]], for code samples.
  *
  * @tparam H the type of the extracted header from a JSON Web Token
  * @tparam C the type of the extracted claim from a JSON Web Token
  *
  * @define token a JSON Web Token as a Base64 url-safe encoded String which can be used inside an HTTP header
  * @define headerString a valid stringified JSON representing the header of the token
  * @define claimString a valid stringified JSON representing the claim of the token
  * @define key the key that will be used to check the token signature
  * @define algo the algorithm to sign the token
  * @define algos a list of possible algorithms that the token can use. See [[http://pauldijou.fr/jwt-scala/#security-concerns Security concerns]] for more infos.
  *
  */
trait JwtCore[H, C] {
  implicit private[jwt] val clock: Clock = Clock.systemUTC
  // Abstract methods
  protected def parseHeader(header: String): H
  protected def parseClaim(claim: String): C

  protected def extractAlgorithm(header: H): Option[JwtAlgorithm]
  protected def extractExpiration(claim: C): Option[Long]
  protected def extractNotBefore(claim: C): Option[Long]

  def encode(header: String, claim: String): String = {
    JwtBase64.encodeString(header) + "." + JwtBase64.encodeString(claim) + "."
  }

  /** Encode a JSON Web Token from its different parts. Both the header and the claim will be encoded to Base64 url-safe, then a signature will be eventually generated from it if you did pass a key and an algorithm, and finally, those three parts will be merged as a single string, using dots as separator.
    *
    * @return $token
    * @param header $headerString
    * @param claim $claimString
    * @param key $key
    * @param algorithm $algo
    */
  def encode(header: String, claim: String, key: String, algorithm: JwtAlgorithm): String = {
    val data = JwtBase64.encodeString(header) + "." + JwtBase64.encodeString(claim)
    data + "." + JwtBase64.encodeString(JwtUtils.sign(data, key, algorithm))
  }

  def encode(header: String, claim: String, key: SecretKey, algorithm: JwtHmacAlgorithm): String = {
    val data = JwtBase64.encodeString(header) + "." + JwtBase64.encodeString(claim)
    data + "." + JwtBase64.encodeString(JwtUtils.sign(data, key, algorithm))
  }

  def encode(header: String, claim: String, key: PrivateKey, algorithm: JwtAsymmetricAlgorithm): String = {
    val data = JwtBase64.encodeString(header) + "." + JwtBase64.encodeString(claim)
    data + "." + JwtBase64.encodeString(JwtUtils.sign(data, key, algorithm))
  }

  /** An alias to `encode` which will provide an automatically generated header.
    *
    * @return $token
    * @param claim $claimString
    */
  def encode(claim: String): String = encode(JwtHeader().toJson, claim)

  /** An alias to `encode` which will provide an automatically generated header and allowing you to get rid of Option
    * for the key and the algorithm.
    *
    * @return $token
    * @param claim $claimString
    * @param key $key
    * @param algorithm $algo
    */
  def encode(claim: String, key: String, algorithm: JwtAlgorithm): String =
    encode(JwtHeader(algorithm).toJson, claim, key, algorithm)

  /** An alias to `encode` which will provide an automatically generated header and allowing you to get rid of Option
    * for the key and the algorithm.
    *
    * @return $token
    * @param claim $claimString
    * @param key $key
    * @param algorithm $algo
    */
  def encode(claim: String, key: SecretKey, algorithm: JwtHmacAlgorithm): String =
    encode(JwtHeader(algorithm).toJson, claim, key, algorithm)

  /** An alias to `encode` which will provide an automatically generated header and allowing you to get rid of Option
    * for the key and the algorithm.
    *
    * @return $token
    * @param claim $claimString
    * @param key $key
    * @param algorithm $algo
    */
  def encode(claim: String, key: PrivateKey, algorithm: JwtAsymmetricAlgorithm): String =
    encode(JwtHeader(algorithm).toJson, claim, key, algorithm)

  /** An alias to `encode` which will provide an automatically generated header and setting both key and algorithm
    * to None.
    *
    * @return $token
    * @param claim the claim of the JSON Web Token
    */
  def encode(claim: JwtClaim): String = encode(claim.toJson)

  /** An alias to `encode` which will provide an automatically generated header and use the claim as a case class.
    *
    * @return $token
    * @param claim the claim of the JSON Web Token
    * @param key $key
    * @param algorithm $algo
    */
  def encode(claim: JwtClaim, key: String, algorithm: JwtAlgorithm): String =
    encode(claim.toJson, key, algorithm)

  /** An alias to `encode` which will provide an automatically generated header and use the claim as a case class.
    *
    * @return $token
    * @param claim the claim of the JSON Web Token
    * @param key $key
    * @param algorithm $algo
    */
  def encode(claim: JwtClaim, key: SecretKey, algorithm: JwtHmacAlgorithm): String =
    encode(claim.toJson, key, algorithm)

  /** An alias to `encode` which will provide an automatically generated header and use the claim as a case class.
    *
    * @return $token
    * @param claim the claim of the JSON Web Token
    * @param key $key
    * @param algorithm $algo
    */
  def encode(claim: JwtClaim, key: PrivateKey, algorithm: JwtAsymmetricAlgorithm): String =
    encode(claim.toJson, key, algorithm)

  /** An alias to `encode` if you want to use case classes for the header and the claim rather than strings, they will just be stringified to JSON format.
    *
    * @return $token
    * @param header the header to stringify as a JSON before encoding the token
    * @param claim the claim to stringify as a JSON before encoding the token
    */
  def encode(header: JwtHeader, claim: JwtClaim): String = header.algorithm match {
    case None => encode(header.toJson, claim.toJson)
    case _ => throw new JwtNonEmptyAlgorithmException()
  }

  /** An alias of `encode` if you only want to pass a string as the key, the algorithm will be deduced from the header.
    *
    * @return $token
    * @param header the header to stringify as a JSON before encoding the token
    * @param claim the claim to stringify as a JSON before encoding the token
    * @param key the secret key to use to sign the token (note that the algorithm will be deduced from the header)
    */
  def encode(header: JwtHeader, claim: JwtClaim, key: String): String = header.algorithm match {
    case Some(algo: JwtAlgorithm) => encode(header.toJson, claim.toJson, key, algo)
    case _ => throw new JwtEmptyAlgorithmException()
  }

  /** An alias of `encode` if you only want to pass a string as the key, the algorithm will be deduced from the header.
    *
    * @return $token
    * @param header the header to stringify as a JSON before encoding the token
    * @param claim the claim to stringify as a JSON before encoding the token
    * @param key the secret key to use to sign the token (note that the algorithm will be deduced from the header)
    */
  def encode(header: JwtHeader, claim: JwtClaim, key: Key): String = (header.algorithm, key) match {
    case (Some(algo: JwtHmacAlgorithm), k: SecretKey) => encode(header.toJson, claim.toJson, k, algo)
    case (Some(algo: JwtAsymmetricAlgorithm), k: PrivateKey) => encode(header.toJson, claim.toJson, k, algo)
    case _ => throw new JwtValidationException("The key type doesn't match the algorithm type. It's either a SecretKey and a HMAC algorithm or a PrivateKey and a RSA or ECDSA algorithm. And an algorithm is required of course.")
  }

  /**
    * @return a tuple of (header64, header, claim64, claim, signature or empty string if none)
    * @throws JwtLengthException if there is not 2 or 3 parts in the token
    */
  private def splitToken(token: String): (String, String, String, String, String) = {
    val parts = JwtUtils.splitString(token, '.')

    val signature = parts.length match {
      case 2 => ""
      case 3 => parts(2)
      case _ => throw new JwtLengthException(s"Expected token [$token] to be composed of 2 or 3 parts separated by dots.")
    }

    (parts(0), JwtBase64.decodeString(parts(0)), parts(1), JwtBase64.decodeString(parts(1)), signature)
  }

  /** Will try to decode a JSON Web Token to raw strings
    *
    * @return if successful, a tuple of 3 strings, the header, the claim and the signature
    * @param token $token
    */
  def decodeRawAll(token: String, options: JwtOptions): Try[(String, String, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(parseHeader(header), parseClaim(claim), signature, options)
    (header, claim, signature)
  }

  def decodeRawAll(token: String): Try[(String, String, String)] = decodeRawAll(token, JwtOptions.DEFAULT)

  /** Will try to decode a JSON Web Token to raw strings using a HMAC algorithm
    *
    * @return if successful, a tuple of 3 strings, the header, the claim and the signature
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decodeRawAll(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Try[(String, String, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(header64, parseHeader(header), claim64, parseClaim(claim), signature, key, algorithms, options)
    (header, claim, signature)
  }

  def decodeRawAll(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Try[(String, String, String)] =
    decodeRawAll(token, key, algorithms, JwtOptions.DEFAULT)

  /** Will try to decode a JSON Web Token to raw strings using an asymmetric algorithm
    *
    * @return if successful, a tuple of 3 strings, the header, the claim and the signature
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decodeRawAll(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Try[(String, String, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(header64, parseHeader(header), claim64, parseClaim(claim), signature, key, algorithms, options)
    (header, claim, signature)
  }

  def decodeRawAll(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm]): Try[(String, String, String)] =
    decodeRawAll(token, key, algorithms, JwtOptions.DEFAULT)

  /** Will try to decode a JSON Web Token to raw strings using a HMAC algorithm
    *
    * @return if successful, a tuple of 3 strings, the header, the claim and the signature
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decodeRawAll(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Try[(String, String, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(header64, parseHeader(header), claim64, parseClaim(claim), signature, key, algorithms, options)
    (header, claim, signature)
  }

  def decodeRawAll(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Try[(String, String, String)] =
    decodeRawAll(token, key, algorithms, JwtOptions.DEFAULT)

  def decodeRawAll(token: String, key: SecretKey, options: JwtOptions): Try[(String, String, String)] =
    decodeRawAll(token, key, JwtAlgorithm.allHmac, options)

  def decodeRawAll(token: String, key: SecretKey): Try[(String, String, String)] =
    decodeRawAll(token, key, JwtOptions.DEFAULT)

  /** Will try to decode a JSON Web Token to raw strings using an asymmetric algorithm
    *
    * @return if successful, a tuple of 3 strings, the header, the claim and the signature
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decodeRawAll(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Try[(String, String, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(header64, parseHeader(header), claim64, parseClaim(claim), signature, key, algorithms, options)
    (header, claim, signature)
  }

  def decodeRawAll(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm]): Try[(String, String, String)] =
    decodeRawAll(token, key, algorithms, JwtOptions.DEFAULT)

  def decodeRawAll(token: String, key: PublicKey, options: JwtOptions): Try[(String, String, String)] =
    decodeRawAll(token, key, JwtAlgorithm.allAsymmetric, options)

  def decodeRawAll(token: String, key: PublicKey): Try[(String, String, String)] =
    decodeRawAll(token, key, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the time)
    *
    * @return if successful, a string representing the JSON version of the claim
    * @param token $token
    */
  def decodeRaw(token: String, options: JwtOptions): Try[String] = decodeRawAll(token, options).map(_._2)

  def decodeRaw(token: String): Try[String] = decodeRaw(token, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the time)
    *
    * @return if successful, a string representing the JSON version of the claim
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decodeRaw(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Try[String] =
    decodeRawAll(token, key, algorithms, options).map(_._2)

  def decodeRaw(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Try[String] =
    decodeRaw(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the time)
    *
    * @return if successful, a string representing the JSON version of the claim
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decodeRaw(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Try[String] =
    decodeRawAll(token, key, algorithms, options).map(_._2)

  def decodeRaw(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm]): Try[String] =
    decodeRaw(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the time)
    *
    * @return if successful, a string representing the JSON version of the claim
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decodeRaw(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Try[String] =
    decodeRawAll(token, key, algorithms, options).map(_._2)

  def decodeRaw(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Try[String] =
    decodeRaw(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the time)
    *
    * @return if successful, a string representing the JSON version of the claim
    * @param token $token
    * @param key $key
    */
  def decodeRaw(token: String, key: SecretKey, options: JwtOptions): Try[String] = decodeRaw(token, key, JwtAlgorithm.allHmac, options)

  def decodeRaw(token: String, key: SecretKey): Try[String] = decodeRaw(token, key, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the time)
    *
    * @return if successful, a string representing the JSON version of the claim
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decodeRaw(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Try[String] =
    decodeRawAll(token, key, algorithms, options).map(_._2)

  def decodeRaw(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm]): Try[String] =
    decodeRaw(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the time)
    *
    * @return if successful, a string representing the JSON version of the claim
    * @param token $token
    * @param key $key
    */
  def decodeRaw(token: String, key: PublicKey, options: JwtOptions): Try[String] = decodeRaw(token, key, JwtAlgorithm.allAsymmetric, options)

  def decodeRaw(token: String, key: PublicKey): Try[String] = decodeRaw(token, key, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return if successful, a tuple representing the header, the claim and eventually the signature
    * @param token $token
    */
  def decodeAll(token: String, options: JwtOptions): Try[(H, C, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    val (h, c) = (parseHeader(header), parseClaim(claim))
    validate(h, c, signature, options)
    (h, c, signature)
  }

  def decodeAll(token: String): Try[(H, C, String)] = decodeAll(token, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return if successful, a tuple representing the header, the claim and eventually the signature
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decodeAll(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Try[(H, C, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    val (h, c) = (parseHeader(header), parseClaim(claim))
    validate(header64, h, claim64, c, signature, key, algorithms, options)
    (h, c, signature)
  }

  def decodeAll(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Try[(H, C, String)] =
    decodeAll(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return if successful, a tuple representing the header, the claim and eventually the signature
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decodeAll(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Try[(H, C, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    val (h, c) = (parseHeader(header), parseClaim(claim))
    validate(header64, h, claim64, c, signature, key, algorithms, options)
    (h, c, signature)
  }

  def decodeAll(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm]): Try[(H, C, String)] =
    decodeAll(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return if successful, a tuple representing the header, the claim and eventually the signature
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decodeAll(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Try[(H, C, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    val (h, c) = (parseHeader(header), parseClaim(claim))
    validate(header64, h, claim64, c, signature, key, algorithms, options)
    (h, c, signature)
  }

  def decodeAll(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Try[(H, C, String)] =
    decodeAll(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return if successful, a tuple representing the header, the claim and eventually the signature
    * @param token $token
    * @param key $key
    */
  def decodeAll(token: String, key: SecretKey, options: JwtOptions): Try[(H, C, String)] =
    decodeAll(token, key, JwtAlgorithm.allHmac, options)

  def decodeAll(token: String, key: SecretKey): Try[(H, C, String)] =
    decodeAll(token, key, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return if successful, a tuple representing the header, the claim and eventually the signature
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decodeAll(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Try[(H, C, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    val (h, c) = (parseHeader(header), parseClaim(claim))
    validate(header64, h, claim64, c, signature, key, algorithms, options)
    (h, c, signature)
  }

  def decodeAll(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm]): Try[(H, C, String)] =
    decodeAll(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return if successful, a tuple representing the header, the claim and eventually the signature
    * @param token $token
    * @param key $key
    */
  def decodeAll(token: String, key: PublicKey, options: JwtOptions): Try[(H, C, String)] =
    decodeAll(token, key, JwtAlgorithm.allAsymmetric, options)

  def decodeAll(token: String, key: PublicKey): Try[(H, C, String)] =
    decodeAll(token, key, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return if successful, the claim of the token in its correct type
    * @param token $token
    */
  def decode(token: String, options: JwtOptions): Try[C] = decodeAll(token, options).map(_._2)

  def decode(token: String): Try[C] = decode(token, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return if successful, the claim of the token in its correct type
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decode(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Try[C] =
    decodeAll(token, key, algorithms, options).map(_._2)

  def decode(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Try[C] =
    decode(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return if successful, the claim of the token in its correct type
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decode(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Try[C] =
    decodeAll(token, key, algorithms, options).map(_._2)

  def decode(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm]): Try[C] =
    decode(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return if successful, the claim of the token in its correct type
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decode(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Try[C] =
    decodeAll(token, key, algorithms, options).map(_._2)

  def decode(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Try[C] =
    decode(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return if successful, the claim of the token in its correct type
    * @param token $token
    * @param key $key
    */
  def decode(token: String, key: SecretKey, options: JwtOptions): Try[C] = decode(token, key, JwtAlgorithm.allHmac, options)

  def decode(token: String, key: SecretKey): Try[C] = decode(token, key, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return if successful, the claim of the token in its correct type
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def decode(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Try[C] =
    decodeAll(token, key, algorithms, options).map(_._2)

  def decode(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm]): Try[C] =
    decode(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return if successful, the claim of the token in its correct type
    * @param token $token
    * @param key $key
    */
  def decode(token: String, key: PublicKey, options: JwtOptions): Try[C] = decode(token, key, JwtAlgorithm.allAsymmetric, options)

  def decode(token: String, key: PublicKey): Try[C] = decode(token, key, JwtOptions.DEFAULT)

  // Validate
  protected def validateTiming(claim: C, options: JwtOptions) {
    val maybeExpiration: Option[Long] =
        if (options.expiration) extractExpiration(claim) else None

    val maybeNotBefore: Option[Long] =
      if (options.notBefore) extractNotBefore(claim) else None

    JwtTime.validateNowIsBetweenSeconds(
      maybeNotBefore.map(_ - options.leeway),
      maybeExpiration.map(_ + options.leeway)
    )
  }

  // Validate if an algorithm is inside the authorized range
  protected def validateHmacAlgorithm(algorithm: JwtHmacAlgorithm, algorithms: Seq[JwtHmacAlgorithm]): Boolean = {
    algorithms.contains(algorithm)
  }

  // Validate if an algorithm is inside the authorized range
  protected def validateAsymmetricAlgorithm(algorithm: JwtAsymmetricAlgorithm, algorithms: Seq[JwtAsymmetricAlgorithm]): Boolean = {
    algorithms.contains(algorithm)
  }

  // Validation when no key and no algorithm (or unknown)
  protected def validate(header: H, claim: C, signature: String, options: JwtOptions) {
    if (options.signature) {
      if (!signature.isEmpty) {
        throw new JwtNonEmptySignatureException()
      }

      extractAlgorithm(header).foreach {
        case JwtUnkwownAlgorithm(name) => throw new JwtNonSupportedAlgorithm(name)
        case _                         => throw new JwtNonEmptyAlgorithmException()
      }
    }

    validateTiming(claim, options)
  }

  // Validation when both key and algorithm
  protected def validate(
    header64: String,
    header: H,
    claim64: String,
    claim: C,
    signature: String,
    options: JwtOptions,
    verify: (Array[Byte], Array[Byte], JwtAlgorithm) => Boolean): Unit = {

    if (options.signature) {
      val maybeAlgo = extractAlgorithm(header)

      if (options.signature && signature.isEmpty) {
        throw new JwtEmptySignatureException()
      } else if (maybeAlgo.isEmpty) {
        throw new JwtEmptyAlgorithmException()
      } else if (!verify(JwtUtils.bytify(header64 +"."+ claim64), JwtBase64.decode(signature), maybeAlgo.get)) {
        throw new JwtValidationException("Invalid signature for this token or wrong algorithm.")
      }
    }
    validateTiming(claim, options)
  }

  // Generic validation on String Key for HMAC algorithms
  protected def validate(header64: String, header: H, claim64: String, claim: C, signature: String, key: String, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Unit = {
    validate(header64, header, claim64, claim, signature, options, (data: Array[Byte], signature: Array[Byte], algorithm: JwtAlgorithm) => algorithm match {
      case algo: JwtHmacAlgorithm => validateHmacAlgorithm(algo, algorithms) && JwtUtils.verify(data, signature, key, algo)
      case _ => false
    })
  }

  // Generic validation on String Key for asymmetric algorithms
  protected def validate(header64: String, header: H, claim64: String, claim: C, signature: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Unit = {
    validate(header64, header, claim64, claim, signature, options, (data: Array[Byte], signature: Array[Byte], algorithm: JwtAlgorithm) => algorithm match {
      case algo: JwtAsymmetricAlgorithm => validateAsymmetricAlgorithm(algo, algorithms) && JwtUtils.verify(data, signature, key, algo)
      case _ => false
    })
  }

  // Validation for HMAC algorithm using a SecretKey
  protected def validate(header64: String, header: H, claim64: String, claim: C, signature: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Unit = {
    validate(header64, header, claim64, claim, signature, options, (data: Array[Byte], signature: Array[Byte], algorithm: JwtAlgorithm) => algorithm match {
      case algo: JwtHmacAlgorithm => validateHmacAlgorithm(algo, algorithms) && JwtUtils.verify(data, signature, key, algo)
      case _ => false
    })
  }

  // Validation for RSA and ECDSA algorithms using PublicKey
  protected def validate(header64: String, header: H, claim64: String, claim: C, signature: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Unit = {
    validate(header64, header, claim64, claim, signature, options, (data: Array[Byte], signature: Array[Byte], algorithm: JwtAlgorithm) => algorithm match {
      case algo: JwtAsymmetricAlgorithm => validateAsymmetricAlgorithm(algo, algorithms) && JwtUtils.verify(data, signature, key, algo)
      case _ => false
    })
  }

  /** Valid a token: doesn't return anything but will thrown exceptions if there are any errors.
    *
    * @param token $token
    * @throws JwtValidationException default validation exception
    * @throws JwtLengthException the number of parts separated by dots is wrong
    * @throws JwtNotBeforeException the token isn't valid yet because its `notBefore` attribute is in the future
    * @throws JwtExpirationException the token isn't valid anymore because its `expiration` attribute is in the past
    * @throws IllegalArgumentException couldn't decode the token since it's not a valid base64 string
    */
  def validate(token: String, options: JwtOptions): Unit = {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(parseHeader(header), parseClaim(claim), signature, options)
  }

  def validate(token: String): Unit = validate(token, JwtOptions.DEFAULT)

  /** An alias of `validate` in case you want to directly pass a string key for HMAC algorithms.
    *
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    * @throws JwtValidationException default validation exception
    * @throws JwtLengthException the number of parts separated by dots is wrong
    * @throws JwtNotBeforeException the token isn't valid yet because its `notBefore` attribute is in the future
    * @throws JwtExpirationException the token isn't valid anymore because its `expiration` attribute is in the past
    * @throws IllegalArgumentException couldn't decode the token since it's not a valid base64 string
    */
  def validate(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Unit = {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(header64, parseHeader(header), claim64, parseClaim(claim), signature, key, algorithms, options)
  }

  def validate(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Unit = validate(token, key, algorithms, JwtOptions.DEFAULT)

  /** An alias of `validate` in case you want to directly pass a string key for asymmetric algorithms.
    *
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    * @throws JwtValidationException default validation exception
    * @throws JwtLengthException the number of parts separated by dots is wrong
    * @throws JwtNotBeforeException the token isn't valid yet because its `notBefore` attribute is in the future
    * @throws JwtExpirationException the token isn't valid anymore because its `expiration` attribute is in the past
    * @throws IllegalArgumentException couldn't decode the token since it's not a valid base64 string
    */
  def validate(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Unit = {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(header64, parseHeader(header), claim64, parseClaim(claim), signature, key, algorithms, options)
  }

  def validate(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm]): Unit = validate(token, key, algorithms, JwtOptions.DEFAULT)

  /** An alias of `validate` in case you want to directly pass a string key.
    *
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    * @throws JwtValidationException default validation exception
    * @throws JwtLengthException the number of parts separated by dots is wrong
    * @throws JwtNotBeforeException the token isn't valid yet because its `notBefore` attribute is in the future
    * @throws JwtExpirationException the token isn't valid anymore because its `expiration` attribute is in the past
    * @throws IllegalArgumentException couldn't decode the token since it's not a valid base64 string
    */
  def validate(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Unit = {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(header64, parseHeader(header), claim64, parseClaim(claim), signature, key, algorithms, options)
  }

  def validate(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Unit = validate(token, key, algorithms, JwtOptions.DEFAULT)

  def validate(token: String, key: SecretKey, options: JwtOptions): Unit = validate(token, key, JwtAlgorithm.allHmac, options)

  def validate(token: String, key: SecretKey): Unit = validate(token, key, JwtOptions.DEFAULT)

  /** An alias of `validate` in case you want to directly pass a string key.
    *
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    * @throws JwtValidationException default validation exception
    * @throws JwtLengthException the number of parts separated by dots is wrong
    * @throws JwtNotBeforeException the token isn't valid yet because its `notBefore` attribute is in the future
    * @throws JwtExpirationException the token isn't valid anymore because its `expiration` attribute is in the past
    * @throws IllegalArgumentException couldn't decode the token since it's not a valid base64 string
    */
  def validate(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Unit = {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(header64, parseHeader(header), claim64, parseClaim(claim), signature, key, algorithms, options)
  }

  def validate(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm]): Unit = validate(token, key, algorithms, JwtOptions.DEFAULT)

  def validate(token: String, key: PublicKey, options: JwtOptions): Unit = validate(token, key, JwtAlgorithm.allAsymmetric, options)

  def validate(token: String, key: PublicKey): Unit = validate(token, key, JwtOptions.DEFAULT)

  /** Test if a token is valid. Doesn't throw any exception.
    *
    * @return a boolean value indicating if the token is valid or not
    * @param token $token
    */
  def isValid(token: String, options: JwtOptions): Boolean =
    try {
      validate(token, options)
      true
    } catch {
      case _ : Throwable => false
    }

  def isValid(token: String): Boolean = isValid(token, JwtOptions.DEFAULT)

  /** An alias for `isValid` if you want to directly pass a string as the key for HMAC algorithms
    *
    * @return a boolean value indicating if the token is valid or not
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def isValid(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Boolean =
    try {
      validate(token, key, algorithms, options)
      true
    } catch {
      case _ : Throwable => false
    }

  def isValid(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Boolean = isValid(token, key, algorithms, JwtOptions.DEFAULT)

  /** An alias for `isValid` if you want to directly pass a string as the key for asymmetric algorithms
    *
    * @return a boolean value indicating if the token is valid or not
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def isValid(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Boolean =
    try {
      validate(token, key, algorithms, options)
      true
    } catch {
      case _ : Throwable => false
    }

  def isValid(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm]): Boolean = isValid(token, key, algorithms, JwtOptions.DEFAULT)

  /** An alias for `isValid` if you want to directly pass a string as the key
    *
    * @return a boolean value indicating if the token is valid or not
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def isValid(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm], options: JwtOptions): Boolean =
    try {
      validate(token, key, algorithms, options)
      true
    } catch {
      case _ : Throwable => false
    }

  def isValid(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Boolean = isValid(token, key, algorithms, JwtOptions.DEFAULT)

  def isValid(token: String, key: SecretKey, options: JwtOptions): Boolean = isValid(token, key, JwtAlgorithm.allHmac, options)

  def isValid(token: String, key: SecretKey): Boolean = isValid(token, key, JwtOptions.DEFAULT)

  /** An alias for `isValid` if you want to directly pass a string as the key
    *
    * @return a boolean value indicating if the token is valid or not
    * @param token $token
    * @param key $key
    * @param algorithms $algos
    */
  def isValid(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm], options: JwtOptions): Boolean =
    try {
      validate(token, key, algorithms, options)
      true
    } catch {
      case _ : Throwable => false
    }

  def isValid(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm]): Boolean = isValid(token, key, algorithms, JwtOptions.DEFAULT)

  def isValid(token: String, key: PublicKey, options: JwtOptions): Boolean = isValid(token, key, JwtAlgorithm.allAsymmetric, options)

  def isValid(token: String, key: PublicKey): Boolean = isValid(token, key, JwtOptions.DEFAULT)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy