JWT.encode и JWT.decode поддерживают HS, RS, PS и ES на 256/384/512, а также ES256K поверх гема openssl для Ruby. EdDSA больше не входит в гем jwt: когда был загружен только jwt 3.3.0, кодирование токена с EdDSA завершилось ошибкой Unsupported signing method, а после добавления jwt-eddsa заработало — но его нативной зависимости ed25519 при установке нужен компилятор C, которого нет в образах контейнеров slim.
Для проверки нужно назвать алгоритм: JWT.decode(token, key, true) без algorithm: завершился ошибкой Expected a different algorithm. Неподписанные токены при проверке отклоняются, а прочитать их можно только с отключённой проверкой (JWT.decode(token, nil, false)), чего никогда не следует делать с токенами, которым код доверяет. Типы ключей проверяются: токен HS256, подделанный с текстом PEM ключа EC, не прошёл проверку с ошибкой HMAC key expected to be a String, когда ключом проверки был объект ключа EC, — но если передать строку PEM как ключ HS256, токен проходил проверку, поэтому храните ключи как объекты OpenSSL::PKey. ES256 не принимает ключ P-384 уже при подписи.
Проверки утверждений нужно включать явно: допуска на расхождение часов нет, а exp необязателен, если не указать его в required_claims, как в примере, где обязательным вместе с ним сделан и издатель. Секреты HMAC принимаются любой длины (с ключом длиной 5 байт подпись и проверка прошли без предупреждения), поэтому проверяйте секреты инструментом проверки секретов JWT.
Подписи ES256 — это 64 байта в формате raw r‖s; подпись DER не проходит проверку с ошибкой Signature verification failed. Вставьте токен в отладчик JWT, чтобы увидеть, в какой кодировке его подпись.