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

t-scala.jwt-core_3.10.0.1.source-code.JwtCore.scala Maven / Gradle / Ivy

The newest version!
package pdi.jwt

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

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
  *   [[https://jwt-scala.github.io/jwt-scala/#security-concerns Security concerns]] for more infos.
  */
trait JwtCore[H, C] {
  implicit private[jwt] def clock: Clock

  // 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 (_, header, _, 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 (_, header, _, 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): Try[Unit] = {
    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 JwtUnknownAlgorithm(name) => throw new JwtNonSupportedAlgorithm(name)
        case _                         => throw new JwtNonEmptyAlgorithmException()
      }
    }

    validateTiming(claim, options).get
  }

  // 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).get
  }

  // 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 (_, header, _, 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)).isSuccess

  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)).isSuccess

  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)).isSuccess

  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)).isSuccess

  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)).isSuccess

  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