Skip to content

Instantly share code, notes, and snippets.

@NyCodeGHG
Last active January 7, 2024 11:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NyCodeGHG/12e8eddfd53e1f4d8e44b79957c1e319 to your computer and use it in GitHub Desktop.
Save NyCodeGHG/12e8eddfd53e1f4d8e44b79957c1e319 to your computer and use it in GitHub Desktop.
ktor JWT testing blog post
package dev.nycode.ktor.jwt.testing
import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
class JWTExtension : ParameterResolver {
private val algorithm = Algorithm.HMAC256("my cool secret")
override fun supportsParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext,
): Boolean {
return parameterContext.parameter.type == JWTVerifier::class.java ||
parameterContext.parameter.type == String::class.java &&
parameterContext.isAnnotated(TestJWT::class.java)
}
override fun resolveParameter(
parameterContext: ParameterContext,
extensionContext: ExtensionContext,
): Any {
return when (parameterContext.parameter.type.kotlin) {
JWTVerifier::class -> {
JWT.require(algorithm).build()
}
String::class -> {
JWT.create()
.sign(algorithm)
}
else -> error("Invalid type.")
}
}
}
import assertk.assertThat
import assertk.assertions.isEqualTo
import com.auth0.jwt.JWTVerifier
import dev.nycode.ktor.auth.PermissionBuilder
import dev.nycode.ktor.jwt.testing.TestJWT
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.testing.*
import org.junit.jupiter.api.Test
internal class JWTTests {
@Test
fun `Request with valid token succeeds`(
verifier: JWTVerifier,
@TestJWT("test") token: String,
) = testApplicationWithJWT(verifier = verifier, token = token)
@Test
fun `Request without token fails with Unauthorized`(verifier: JWTVerifier) =
testApplicationWithJWT(
verifier = verifier,
token = null,
expectedCode = HttpStatusCode.Unauthorized
)
}
internal fun testApplicationWithJWT(
verifier: JWTVerifier,
token: String?,
routing: Routing.() -> Unit = {
authenticate {
handle {
call.respond(HttpStatusCode.OK)
}
}
},
expectedCode: HttpStatusCode = HttpStatusCode.OK,
request: HttpRequestBuilder.() -> Unit = {
token?.let(::bearerAuth)
},
block: suspend ApplicationTestBuilder.(HttpResponse) -> Unit = {},
) = testApplication {
install(Authentication) {
jwt {
validate { JWTPrincipal(it.payload) }
verifier(verifier)
}
}
routing(routing)
val response = client.get("/", request)
assertThat(response.status).isEqualTo(expectedCode)
block(response)
}
package dev.nycode.ktor.jwt.testing
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class TestJWT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment