diff --git a/CHANGELOG.md b/CHANGELOG.md index 4efb1dd8..2edf516a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [v3.3.0](https://github.com/jwt/ruby-jwt/tree/v3.3.0) (NEXT) +## [v3.3.0](https://github.com/jwt/ruby-jwt/tree/v3.3.0) (2026-09-11) [Full Changelog](https://github.com/jwt/ruby-jwt/compare/v3.2.0...v3.3.0) @@ -8,7 +8,6 @@ - Allow a leeway to be given for the `iat` claim verification [#747](https://github.com/jwt/ruby-jwt/pull/747) - ([@denis1011101](https://github.com/denis1011101)) - Revamp the error hierarchy under a new `JWT::Error` base class; signing failures now consistently raise `JWT::EncodeError`, see [UPGRADING.md](UPGRADING.md) [#722](https://github.com/jwt/ruby-jwt/pull/722) ([@anakinj](https://github.com/anakinj)) -- Your contribution here **Fixes and enhancements:** diff --git a/UPGRADING.md b/UPGRADING.md index 8fa88ce8..474383cb 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -8,15 +8,29 @@ The [error classes were reorganised](https://github.com/jwt/ruby-jwt/pull/722) u - `JWT::TokenError` covers every failure in processing a token, and splits into `JWT::MalformedTokenError` (the token is structurally invalid), `JWT::SignatureError` (signature and algorithm problems) and `JWT::ClaimValidationError` (a claim did not verify). - `JWT::VerificationKeyError`, a subclass of `JWT::VerificationError`, says the key or algorithm given for verification cannot be used, as opposed to a signature that does not match. -### Decoding is unaffected +### Backwards compatibility -`JWT::DecodeError` is deprecated in favour of the classes above, but it keeps its meaning: every error class except `JWT::EncodeError` still inherits from it. A `rescue JWT::DecodeError` around `JWT.decode` catches everything it caught before, and the specific classes it has always raised, such as `JWT::ExpiredSignature`, are unchanged. There is nothing to do on the decoding side. +This is a backwards compatible change for effectively every application. The new classes were inserted above the existing ones rather than replacing them, so every error class you already rescue keeps its name, its meaning and everything it used to catch. In the ordinary case, upgrading needs no code change at all. -Verification also became more predictable: `RS*` and `PS*` now reject a key of the wrong type with a `JWT::VerificationKeyError` instead of letting a `NoMethodError` escape. +There is one exception, and it is narrow enough to be worth stating precisely. It applies only if all three of the following are true: + +1. You rescue around `JWT.encode`, not around `JWT.decode`. +2. The class you rescue is `JWT::DecodeError`, `JWT::IncorrectAlgorithm`, `JWT::UnsupportedEcdsaCurve` or `ArgumentError`. +3. That rescue is reached at all, which takes a key or algorithm that cannot sign in the first place. + +If any one of the three does not hold, there is nothing to do. If all three do, the fix is a one line change, described in the next section. + +Why that is a small target in practice: + +- **Decoding is untouched.** `JWT::DecodeError` is deprecated in favour of the classes above, but it keeps its meaning: every error class except `JWT::EncodeError` still inherits from it. A `rescue JWT::DecodeError` around `JWT.decode` catches everything it caught before, and the specific classes it has always raised, such as `JWT::ExpiredSignature`, are unchanged. +- **Signing that currently works is untouched.** Every case in the table below is an unusable key or algorithm, a misconfiguration that fails on every call made with that key. None of them can be triggered by a particular payload or token, so an application that signs tokens successfully today does not reach them at all. +- **Failures you let escape are untouched.** All of these raised before and still raise; only the class changed. Code that does not rescue them behaves exactly as it did. + +One decode-side behaviour did change, but in the direction of catching more: `RS*` and `PS*` now reject a key of the wrong type with a `JWT::VerificationKeyError`, which is a `JWT::DecodeError`, instead of letting a `NoMethodError` escape. ### Signing failures now raise `JWT::EncodeError` -This is the part that can break. Signing failures used to surface as decode errors, and are now consistently `JWT::EncodeError`, which is deliberately not a `JWT::DecodeError`: +Signing failures used to surface as decode errors, and are now consistently `JWT::EncodeError`, which is deliberately not a `JWT::DecodeError`: | Signing with | Used to raise | Now raises | | --- | --- | --- | @@ -28,6 +42,16 @@ This is the part that can break. Signing failures used to surface as decode erro If you wrap `JWT.encode` in `rescue JWT::DecodeError`, `rescue JWT::IncorrectAlgorithm` or `rescue JWT::UnsupportedEcdsaCurve`, rescue `JWT::EncodeError` or `JWT::Error` instead. +### Why this is a minor release + +The exception described above is a real incompatibility, and 3.3.0 is still deliberately a minor release rather than a new major. + +The reason is that nothing here turns a call that used to succeed into one that fails, or the other way round. Every case in the table raised an error before and raises an error now, only under a different class. No token is signed that would previously have been refused, no token verifies that would previously have been rejected, and no signature is produced or accepted on different terms than before. What changed is which `rescue` clause matches on a path that was already failing. + +Weighed against the cost of a second major migration so soon after 3.0.0, that did not seem to warrant one. + +If you find a case where this change affects whether a call succeeds, rather than which error it raises when it fails, please [open an issue](https://github.com/jwt/ruby-jwt/issues). That would be a bug rather than an intended consequence of the reorganisation. + # Upgrading ruby-jwt to >= 3.0.0 ## Removal of the indirect [RbNaCl](https://github.com/RubyCrypto/rbnacl) dependency