Revision history for Catalyst-Plugin-OAuth2-AuthorizationServer 0.003 2026-07-16 - Genericise the example OAuth scopes (now example:*) and remove downstream-application references from the README and the perl_critic author test comment, so the distribution reads as standalone. Examples and tests only; no functional change. 0.002 Unreleased - Version bump so consumers reliably install the current code over any 0.001 already present in an environment (cpanm treats same-version as satisfied); no code change from the 0.001 line below. - Initial MCP-profile OAuth 2.1 Authorization Server. - INCOMPATIBLE: code_verifier is now validated as 43-128 characters of the RFC 7636 4.1 unreserved set before the PKCE comparison. A client sending a shorter or otherwise non-conformant verifier that worked before now gets invalid_grant. - INCOMPATIBLE: the Store contract has changed; any implementation of Role::Store must be updated. rotate_refresh_token now returns undef (unknown/expired), { binding => \%binding } (was live, now revoked), or { binding => \%binding, reused => 1 } (a replay), instead of a bare binding or undef. A rotated token must be retained as a tombstone until its original expires_at rather than deleted, or reuse detection silently stops working. create_refresh_token gains family_id in the binding and must return false, persisting nothing, if that family has been revoked. revoke_family($family_id) is a new required verb. - Refresh-token reuse now revokes the whole token family (RFC 9700): replaying a rotated token revokes every token descended from the same authorization, including the one the legitimate client holds. The error is deliberately identical to an unknown token, so it gives an attacker no signal that reuse was detected. - Refresh rotation and family revocation are safe against a concurrent replay: a rotation in flight when revoke_family runs can no longer create its successor into the revoked family. Store implementers must make create_refresh_token's check-and-insert mutually exclusive with revoke_family for a family (SERIALIZABLE or a per-family lock); a bare INSERT ... WHERE NOT EXISTS write-skews under READ COMMITTED. - Access tokens carry a jti claim. Nothing reads it; it exists so a revocation denylist can be added later without changing the token format for tokens already issued. - signing_key length is now validated against the RFC 7518 3.2 minimum for the configured HS algorithm (32/48/64 bytes). A short key was previously accepted and produced brute-forceable tokens. - A request that repeats a parameter is now rejected with invalid_request (RFC 6749 3.2.1) instead of silently taking the last value. On /authorize the rejection renders directly and never redirects, since it happens before redirect_uri is validated. - Dynamic Client Registration rejects unsupported metadata values with invalid_client_metadata (RFC 7591 2) instead of storing them verbatim. The allow-lists are derived from the metadata document, so registration cannot accept what discovery does not advertise. - LIMITATIONS now states that family revocation does not kill access tokens already minted from the family (they are stateless JWTs and live out access_ttl), and that a concurrent double-refresh is indistinguishable from a replay and will revoke the family.