Summary
auth.APIKeyTokenVerifier.Verify accepts a correctly signed token whose iat (Issued At) claim is far in the future when the token omits nbf. This appears to be a behavior regression from the switch to github.com/golang-jwt/jwt/v5.
Reproduction
I verified this against livekit/livekit-server:latest (v1.13.5) running locally in development mode. I signed this token with the server’s configured API secret:
futureIssuedAt := time.Now().Add(2 * time.Hour)
token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
Issuer: "devkey",
Subject: "future-iat-test",
IssuedAt: jwt.NewNumericDate(futureIssuedAt),
ExpiresAt: jwt.NewNumericDate(futureIssuedAt.Add(time.Hour)),
// nbf intentionally omitted
}).SignedString([]byte("secret"))
The token also carried the roomList grant. An immediate request to RoomService.ListRooms returned 200 OK, despite the iat being two hours ahead.
Impact
This is not an unsigned-token or external-forgery vulnerability: producing the token still requires the API secret. However, a trusted/custom token issuer can create a future-dated token that is usable immediately, rather than at its stated issuance time. Normal tokens minted by auth.AccessToken.ToJWT are protected because they set nbf = now; the gap affects custom minters that set iat but omit nbf.
Proposed fix
Add jwt.WithIssuedAt() to the existing jwt.ParseWithClaims options in auth/verifier.go, next to jwt.WithLeeway(tokenLeeway) and jwt.WithExpirationRequired().
jwt/v5 does not validate iat by default. WithIssuedAt() validates the claim when present, while preserving compatibility with tokens that omit this optional claim. The existing one-minute leeway would continue to cover small clock skew.
Please add a regression test for a correctly signed token with an iat two hours in the future and no nbf.
Summary
auth.APIKeyTokenVerifier.Verifyaccepts a correctly signed token whoseiat(Issued At) claim is far in the future when the token omitsnbf. This appears to be a behavior regression from the switch togithub.com/golang-jwt/jwt/v5.Reproduction
I verified this against
livekit/livekit-server:latest(v1.13.5) running locally in development mode. I signed this token with the server’s configured API secret:The token also carried the
roomListgrant. An immediate request toRoomService.ListRoomsreturned200 OK, despite theiatbeing two hours ahead.Impact
This is not an unsigned-token or external-forgery vulnerability: producing the token still requires the API secret. However, a trusted/custom token issuer can create a future-dated token that is usable immediately, rather than at its stated issuance time. Normal tokens minted by
auth.AccessToken.ToJWTare protected because they setnbf = now; the gap affects custom minters that setiatbut omitnbf.Proposed fix
Add
jwt.WithIssuedAt()to the existingjwt.ParseWithClaimsoptions inauth/verifier.go, next tojwt.WithLeeway(tokenLeeway)andjwt.WithExpirationRequired().jwt/v5does not validateiatby default.WithIssuedAt()validates the claim when present, while preserving compatibility with tokens that omit this optional claim. The existing one-minute leeway would continue to cover small clock skew.Please add a regression test for a correctly signed token with an
iattwo hours in the future and nonbf.