JWT.encode e JWT.decode cobrem HS, RS, PS e ES em 256/384/512 e o ES256K, sobre a gem openssl do Ruby. O EdDSA não faz mais parte da gem jwt: com apenas a jwt 3.3.0 carregada, a codificação com EdDSA falhou com Unsupported signing method e passou a funcionar depois de adicionar a jwt-eddsa — cuja dependência nativa ed25519 precisa de um compilador C na instalação, algo que imagens de contêiner enxutas não têm.
A verificação exige indicar o algoritmo: JWT.decode(token, key, true) sem algorithm: falhou com Expected a different algorithm. Tokens sem assinatura são recusados na verificação e só podem ser lidos com a verificação desativada (JWT.decode(token, nil, false)), o que o código nunca deve fazer com tokens em que confia. Os tipos de chave são conferidos: um token HS256 forjado com o texto PEM da chave EC falhou com HMAC key expected to be a String quando a chave de verificação era o objeto da chave EC — mas, com a string PEM passada como chave HS256, o token foi verificado; por isso, mantenha as chaves como objetos OpenSSL::PKey. O ES256 recusa uma chave P-384 no momento da assinatura.
As verificações de claims precisam ser ativadas explicitamente: não há tolerância, e exp é opcional, a menos que esteja listada em required_claims, como o exemplo faz junto com o emissor. Segredos HMAC de qualquer tamanho são aceitos — uma chave de 5 bytes assinou e verificou sem nenhum aviso —, então teste os segredos com o verificador de segredos JWT.
As assinaturas ES256 são 64 bytes de r‖s brutos; uma assinatura DER falha com Signature verification failed. Cole um token no depurador de JWT para ver qual codificação ele usa.