Skip to content

Commit

Permalink
allow type header to be omitted when parsing JWT strings (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
nefilim authored Jan 17, 2023
1 parent 4c3db4b commit bb65a3a
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 2 deletions.
7 changes: 5 additions & 2 deletions core/src/main/kotlin/io/github/nefilim/kjwt/JWT.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ fun String?.toJWTKeyID(): JWTKeyID? = this?.let { JWTKeyID(it)}
@Serializable
data class JOSEHeader<T: JWSAlgorithm>(
@SerialName("alg") @Serializable(JWSAlgorithmSerializer::class) val algorithm: T,
@SerialName("typ") @Serializable(JOSETypeSerializer::class) val type: JOSEType,
@SerialName("typ") @Serializable(JOSETypeSerializer::class) val type: JOSEType? = null,
@SerialName("kid") val keyID: JWTKeyID? = null,
) {
fun toJSON(): String {
Expand Down Expand Up @@ -104,6 +104,7 @@ class JWT<T: JWSAlgorithm> private constructor(
prettyPrint = true
}

internal fun es256WithoutTypeHeader(claims: JWTClaimSetBuilder.() -> Unit): JWT<JWSES256Algorithm> = buildJWT(JOSEHeader(JWSES256Algorithm), claims)
fun es256(keyID: JWTKeyID? = null, claims: JWTClaimSetBuilder.() -> Unit): JWT<JWSES256Algorithm> = buildJWT(JOSEHeader(JWSES256Algorithm, JOSEType.JWT, keyID), claims)
fun es256k(keyID: JWTKeyID? = null, claims: JWTClaimSetBuilder.() -> Unit): JWT<JWSES256KAlgorithm> = buildJWT(JOSEHeader(JWSES256KAlgorithm, JOSEType.JWT, keyID), claims)
fun es384(keyID: JWTKeyID? = null, claims: JWTClaimSetBuilder.() -> Unit): JWT<JWSES384Algorithm> = buildJWT(JOSEHeader(JWSES384Algorithm, JOSEType.JWT, keyID), claims)
Expand Down Expand Up @@ -131,7 +132,9 @@ class JWT<T: JWSAlgorithm> private constructor(

val h = Either.catch {
format.decodeFromString(JOSEHeader.serializer(PolymorphicSerializer(JWSAlgorithm::class)), jwtDecodeString(parts[0]))
}.mapLeft { KJWTVerificationError.AlgorithmMismatch }.bind()
}.mapLeft {
println(it)
KJWTVerificationError.AlgorithmMismatch }.bind()
val claims = Either.catch { format.parseToJsonElement(jwtDecodeString(parts[1])) }.mapLeft { KJWTVerificationError.InvalidJWT }.bind()
val claimsMap = Either.catch { (claims as JsonObject) }.mapLeft { KJWTVerificationError.EmptyClaims }.bind()

Expand Down
28 changes: 28 additions & 0 deletions core/src/test/kotlin/io/github/nefilim/kjwt/JWTSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.github.nefilim.kjwt.ClaimsVerification.requiredOptionClaim
import io.github.nefilim.kjwt.ClaimsVerification.subject
import io.github.nefilim.kjwt.ClaimsVerification.validateClaims
import io.github.nefilim.kjwt.JWT.Companion.es256
import io.github.nefilim.kjwt.JWT.Companion.es256WithoutTypeHeader
import io.github.nefilim.kjwt.JWT.Companion.es256k
import io.github.nefilim.kjwt.JWT.Companion.es384
import io.github.nefilim.kjwt.JWT.Companion.es512
Expand Down Expand Up @@ -136,6 +137,33 @@ class JWTSpec: WordSpec() {
}
}

"decode JWT with missing type header" {
val rawJWT = es256WithoutTypeHeader {
subject("1234567890")
issuedAt(LocalDateTime.ofInstant(Instant.ofEpochSecond(1516239022), ZoneId.of("UTC")))
}
// create a token with a spec violating lowercase type of "jwt"
val jwtString = listOf(
"""
{
"alg": "${rawJWT.header.algorithm.headerID}"
}
""".trimIndent(),
"""
{
"sub": "${rawJWT.subject().getOrElse { "" }}",
"iat": ${rawJWT.issuedAt().map { it.toEpochSecond(ZoneOffset.UTC) }.getOrElse { 0 }}
}
""".trimIndent()
).joinToString(".") {
jwtEncodeBytes(it.toByteArray(Charsets.UTF_8))
}
JWT.decode(jwtString).shouldBeRight().also {
it.parts.size shouldBe 2
it.jwt shouldBe rawJWT
}
}

"support arbitrary JSON claim values" {
val thelist = listOf("tagA", "tagB", "tagC")
val rawJWT = es256 {
Expand Down

0 comments on commit bb65a3a

Please sign in to comment.