Insecurely generated token - JWT
Need
Signing of every JSON Web Token with a strong algorithm and a secret supplied at runtime
Context
• Usage of Kotlin 1.9+ on the JVM for building application services
• Usage of the Auth0 java-jwt library for issuing JSON Web Tokens
Description
1. Non compliant code
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
fun createToken(userId: String): String =
// The "none" algorithm produces tokens that anyone can forge
JWT.create()
.withSubject(userId)
.sign(Algorithm.none())The `createToken` function below issues JSON Web Tokens signed with `Algorithm.none()` from the Auth0 java-jwt library. The `none` algorithm produces a token with an empty signature. Anyone can create such a token, with any subject, role or expiration, without knowing any secret. If any service that consumes these tokens accepts the `none` algorithm, or skips signature verification because the tokens are "internal", an attacker can impersonate any user by writing the token by hand. Unsigned tokens also cannot be revoked or trusted after they leave the issuer, since there is no way to tell a genuine token from a forged one.
2. Steps
• Never sign tokens with `Algorithm.none()`.
• Sign with `Algorithm.HMAC256` and a secret of at least 32 bytes, or with an asymmetric algorithm such as `Algorithm.ECDSA256` or `Algorithm.RSA256`.
• Load the signing secret or private key at runtime from a secrets manager, never from the source code.
• Include issuer, issued-at and expiration claims in every token.
• Verify tokens with `JWT.require(algorithm)` using the expected algorithm, issuer and audience, and reject any other algorithm.
3. Secure code example
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import java.time.Instant
private const val MIN_SECRET_BYTES = 32
private const val TOKEN_LIFETIME_SECONDS = 15L * 60
// HMAC-SHA256 with a secret injected at runtime; refuse to start without it...The corrected code signs every token with HMAC-SHA256. The secret is read at runtime from the `JWT_SECRET` environment variable, which the deployment injects from a secrets manager, and the code refuses to start when it is missing or shorter than 32 bytes, the minimum key length for HS256. The algorithm is built once and shared. Each token also carries an issuer, an issue time and an expiration 15 minutes later, so consumers can validate where it comes from and stop accepting it after a short time. Services that verify the tokens must use `JWT.require(algorithm)` with the same algorithm, which rejects tokens signed with `none` or with any other algorithm. When several services only need to verify tokens, an asymmetric algorithm such as `Algorithm.ECDSA256` keeps the signing key with the issuer.
References
• 309. Insecurely generated token - JWT