github.com/go-jose/go-jose/v4 (the continuation of Square's go-jose) implements JWS, JWE, JWK and JWT, which is why it shows up wherever Go services exchange encrypted or signed tokens. 4.1.6 signed and verified the HS, RS, PS and ES families and EdDSA; ES256K, Ed25519 and Ed448 are not recognised algorithm names.
Version 4 made the algorithm list mandatory: jwt.ParseSigned(token, nil) fails with no signature algorithms specified, so a token can only be parsed against algorithms you chose. Unsigned tokens can be parsed if none is in that list, but Claims(key) never verifies them — the only way to read one is the explicitly named UnsafeClaimsWithoutVerification.
Verification is split in two, and that is the trap. token.Claims(key, &claims) checks the signature and fills the struct — it returned the claims of a token that had expired an hour earlier without any error. Expiry, not-before, issuer and audience are checked only by claims.Validate() with a jwt.Expected value or ValidateWithLeeway, and a token without exp passes even that, so check that Expiry is set, as the sample does. Validate allows jwt.DefaultLeeway, one minute, which the check confirmed.
Keys are typed. An HS256 token keyed with the PEM text failed against the *ecdsa.PublicKey with error in cryptographic primitive, but verified when the same PEM bytes were passed as the key — keep keys as parsed Go keys, not bytes. ES256 refuses a P-384 key with expected 256 bit key, got 384 bits instead. A DER-encoded ES256 signature fails with the same generic error in cryptographic primitive as a forgery; the JWT debugger shows the signature length.
For encrypted tokens, the JWE inspector shows the header and algorithms; for key sets, the JWKS checker shows what a provider publishes and the JWK converter moves keys between JWK and PEM.