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

org.radarbase.auth.jwks.JwksTokenVerifierLoader.kt Maven / Gradle / Ivy

package org.radarbase.auth.jwks

import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.auth0.jwt.interfaces.Verification
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import org.radarbase.auth.authentication.TokenVerifier
import org.radarbase.auth.authentication.TokenVerifierLoader
import org.radarbase.auth.exception.TokenValidationException
import org.radarbase.auth.jwt.JwtTokenVerifier
import org.radarbase.auth.jwt.JwtTokenVerifier.Companion.SCOPE_CLAIM
import org.slf4j.LoggerFactory
import java.time.Duration

class JwksTokenVerifierLoader(
    private val url: String,
    private val resourceName: String,
    private val algorithmParser: JwkParser,
) : TokenVerifierLoader {

    override suspend fun fetch(): List {
        val keySet = try {
            fetchPublicKeyInfo()
        } catch (ex: Exception) {
            logger.warn("Failed to fetch token for {}: {}", url, ex.message)
            return listOf()
        }
        return buildList(keySet.keys.size) {
            keySet.keys.forEach { key ->
                try {
                    add(
                        algorithmParser.parse(key)
                            .toTokenVerifier(resourceName)
                    )
                } catch (ex: Exception) {
                    logger.error("Failed to parse key from {}: {}", url, ex.message)
                }
            }
        }
    }

    private suspend fun fetchPublicKeyInfo(): JsonWebKeySet = withContext(Dispatchers.IO) {
        logger.info("Getting the JWT public key at {}", url)
        val response = httpClient.request(url)

        if (!response.status.isSuccess()) {
            throw TokenValidationException("Cannot fetch token keys (${response.status}) - ${response.bodyAsText()}")
        }

        response.body()
    }

    override fun toString(): String = "MPTokenKeyAlgorithmKeyLoader"

    companion object {
        @JvmStatic
        @JvmOverloads
        fun Algorithm.toTokenVerifier(resourceName: String, builder: Verification.() -> Unit = {}): JwtTokenVerifier {
            val verifier = JWT.require(this).run {
                withClaimPresence(SCOPE_CLAIM)
                withAudience(resourceName)
                builder()
                build()
            }
            return JwtTokenVerifier(name, verifier)
        }

        private val logger = LoggerFactory.getLogger(JwksTokenVerifierLoader::class.java)

        private val httpClient = HttpClient(CIO).config {
            install(HttpTimeout) {
                connectTimeoutMillis = Duration.ofSeconds(10).toMillis()
                socketTimeoutMillis = Duration.ofSeconds(10).toMillis()
                requestTimeoutMillis = Duration.ofSeconds(30).toMillis()
            }
            install(ContentNegotiation) {
                json(Json {
                    ignoreUnknownKeys = true
                    coerceInputValues = true
                })
            }
            defaultRequest {
                accept(ContentType.Application.Json)
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy