v0.14 is out: a mobile auth pack for Swift/iOS and Android, catching insecure token storage, cleartext traffic, and OAuth in embedded WebViews. Read more →
MEDIUM AI PREVALENCE: MEDIUM auth.kotlin.jwt.missing-issuer-audience

This JWT verifier checks the signature but never asserts the token's intended recipient: the Auth0 JWT.require(alg)...build() chain pins no .withIssuer(...) and no .withAudience(...).

Why AI tools produce this: AI coding tools produce this regularly, typically when prompted for a shortcut or a quick fix.

Why this matters

A valid signature only proves the token was minted by whoever holds the key, not that it was issued by the expected authority or meant for THIS service. A token your provider issued for a different audience, or one minted by any party that shares the key, will still verify, enabling token replay across services (CWE-345). This is a common AI-generated mistake in Ktor jwt { verifier(...) } setups: the sample verifies the signature and stops there.

Pin the recipient before .build(): chain .withIssuer("https://your-idp") and .withAudience("your-api") on the JWT.require(...) builder.

VULNERABLE
vulnerable.kt
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*

fun Application.configureSecurity(secret: String) {
    install(Authentication) {
        jwt("auth-jwt") {
            // ruleid: auth.kotlin.jwt.missing-issuer-audience
            verifier(
                JWT.require(Algorithm.HMAC256(secret))
                    .acceptLeeway(3)
                    .build()
            )
            validate { credential -> JWTPrincipal(credential.payload) }
        }
    }
}
SAFE
safe.kt
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*

fun Application.configureSecurity(secret: String, issuer: String, audience: String) {
    install(Authentication) {
        jwt("auth-jwt") {
            verifier(
                JWT.require(Algorithm.HMAC256(secret))
                    .withIssuer(issuer)
                    .withAudience(audience)
                    .acceptLeeway(3)
                    .build()
            )
            validate { credential ->
                if (credential.payload.audience.contains(audience)) {
                    JWTPrincipal(credential.payload)
                } else {
                    null
                }
            }
        }
    }
}

Suppressing this rule

If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.

// oauthlint-disable-next-line auth.kotlin.jwt.missing-issuer-audience -- <reason>

References

https://ktor.io/docs/server-jwt.html ↗https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3 ↗https://cwe.mitre.org/data/definitions/345.html ↗