diff --git a/core/src/main/kotlin/io/github/nefilim/kjwt/JWT.kt b/core/src/main/kotlin/io/github/nefilim/kjwt/JWT.kt index 9d0c1a1..56b46ba 100644 --- a/core/src/main/kotlin/io/github/nefilim/kjwt/JWT.kt +++ b/core/src/main/kotlin/io/github/nefilim/kjwt/JWT.kt @@ -35,7 +35,7 @@ fun String?.toJWTKeyID(): JWTKeyID? = this?.let { JWTKeyID(it)} @Serializable data class JOSEHeader( @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 { @@ -104,6 +104,7 @@ class JWT private constructor( prettyPrint = true } + internal fun es256WithoutTypeHeader(claims: JWTClaimSetBuilder.() -> Unit): JWT = buildJWT(JOSEHeader(JWSES256Algorithm), claims) fun es256(keyID: JWTKeyID? = null, claims: JWTClaimSetBuilder.() -> Unit): JWT = buildJWT(JOSEHeader(JWSES256Algorithm, JOSEType.JWT, keyID), claims) fun es256k(keyID: JWTKeyID? = null, claims: JWTClaimSetBuilder.() -> Unit): JWT = buildJWT(JOSEHeader(JWSES256KAlgorithm, JOSEType.JWT, keyID), claims) fun es384(keyID: JWTKeyID? = null, claims: JWTClaimSetBuilder.() -> Unit): JWT = buildJWT(JOSEHeader(JWSES384Algorithm, JOSEType.JWT, keyID), claims) @@ -131,7 +132,9 @@ class JWT 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() diff --git a/core/src/test/kotlin/io/github/nefilim/kjwt/JWTSpec.kt b/core/src/test/kotlin/io/github/nefilim/kjwt/JWTSpec.kt index 2e5e061..176f68a 100644 --- a/core/src/test/kotlin/io/github/nefilim/kjwt/JWTSpec.kt +++ b/core/src/test/kotlin/io/github/nefilim/kjwt/JWTSpec.kt @@ -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 @@ -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 {