JWT.encode and JWT.decode cover HS, RS, PS and ES at 256/384/512 and ES256K on top of Ruby's openssl gem. EdDSA is no longer part of the jwt gem: with only jwt 3.3.0 loaded, encoding EdDSA failed with Unsupported signing method, and it worked after adding jwt-eddsa — whose native ed25519 dependency needs a C compiler at install time, which slim container images lack.
Verification requires naming the algorithm: JWT.decode(token, key, true) without algorithm: failed with Expected a different algorithm. Unsigned tokens are refused when verifying, and readable only with verification switched off (JWT.decode(token, nil, false)), which code should never do for tokens it trusts. Key types are checked: an HS256 token forged with the EC key's PEM text failed with HMAC key expected to be a String when the verification key was the EC key object — but passing the PEM string as the HS256 key verified it, so keep keys as OpenSSL::PKey objects. ES256 refuses a P-384 key at signing time.
Claims checks are opt-in: no leeway, and exp is optional unless listed in required_claims, as the sample does together with the issuer. HMAC secrets of any length are accepted — a 5-byte key signed and verified without a warning — so check secrets with the JWT secret checker.
ES256 signatures are 64 bytes of raw r‖s; a DER signature fails with Signature verification failed. Paste a token into the JWT debugger to see which encoding it carries.