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

de.innfactory.akka.jwt.ConfigurableJwtValidator.scala Maven / Gradle / Ivy

package de.innfactory.akka.jwt

import java.text.ParseException

import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.jwk.source.JWKSource
import com.nimbusds.jose.proc.{JWSVerificationKeySelector, SecurityContext}
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.proc.{
  BadJWTException,
  DefaultJWTClaimsVerifier,
  DefaultJWTProcessor
}

import scala.util.{Failure, Success, Try}

object ConfigurableJwtValidator {
  def apply(
      keySource: JWKSource[SecurityContext],
      maybeCtx: Option[SecurityContext] = None,
      additionalChecks: List[(JWTClaimsSet,
                              SecurityContext) => Option[BadJWTException]] =
        List.empty
  ): ConfigurableJwtValidator =
    new ConfigurableJwtValidator(keySource, maybeCtx, additionalChecks)
}

/**
  * A configurable JwtValidator implementation.
  *
  * The Nimbus code come from this example:
  *   https://connect2id.com/products/nimbus-jose-jwt/examples/validating-jwt-access-tokens
  *
  * @param keySource (Required) JSON Web Key (JWK) source.
  * @param maybeCtx (Optional) Security context. Default is `null` (no Security Context).
  * @param additionalChecks (Optional) List of additional checks that will be executed on the JWT token passed. Default is an empty List.
  */
final class ConfigurableJwtValidator(
    keySource: JWKSource[SecurityContext],
    maybeCtx: Option[SecurityContext] = None,
    additionalChecks: List[(JWTClaimsSet,
                            SecurityContext) => Option[BadJWTException]] =
      List.empty
) extends JwtValidator {

  // Set up a JWT processor to parse the tokens and then check their signature
  // and validity time window (bounded by the "iat", "nbf" and "exp" claims)
  private val jwtProcessor = new DefaultJWTProcessor[SecurityContext]
  // The expected JWS algorithm of the access tokens (agreed out-of-band)
  private val expectedJWSAlg = JWSAlgorithm.RS256
  // Configure the JWT processor with a key selector to feed matching public
  // RSA keys sourced from the JWK set URL
  private val keySelector =
    new JWSVerificationKeySelector[SecurityContext](expectedJWSAlg, keySource)
  jwtProcessor.setJWSKeySelector(keySelector)

  // Set the additional checks.
  //
  // Updated and adapted version of this example:
  //   https://connect2id.com/products/nimbus-jose-jwt/examples/validating-jwt-access-tokens#claims-validator
  jwtProcessor.setJWTClaimsSetVerifier(
    new DefaultJWTClaimsVerifier[SecurityContext] {
      override def verify(claimsSet: JWTClaimsSet,
                          context: SecurityContext): Unit = {
        super.verify(claimsSet, context)

        additionalChecks.toStream
          .map(f => f(claimsSet, context))
          .collect { case Some(e) => e }
          .foreach(e => throw e)
      }
    })

  private val ctx: SecurityContext = maybeCtx.orNull

  override def validate(
      jwtToken: JwtToken): Either[BadJWTException, (JwtToken, JWTClaimsSet)] = {
    val content: String = jwtToken.content
    if (content.isEmpty) Left(EmptyJwtTokenContent)
    else
      Try(jwtProcessor.process(content, ctx)) match {
        case Success(claimSet: JWTClaimsSet) => Right(jwtToken -> claimSet)
        case Failure(e: BadJWTException)     => Left(e)
        case Failure(_: ParseException)      => Left(InvalidJwtToken)
        case Failure(e: Exception)           => Left(UnknownException(e))
      }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy