本文へスキップ
ecdsa.com
Ruby

ruby-jwt (jwt gem)

ruby-jwt は、ほとんどの Ruby・Rails アプリケーションで使われている jwt gem です。algorithm の明示が必須で、鍵の種類がチェックされ、ES256K も含まれます。ただし EdDSA は 3.x で別の gem に移り、短い HMAC シークレットも警告なしで受け入れられます。
ライセンス
MIT
確認したバージョン
3.3.0 ·
ソースコード
github.com/jwt/ruby-jwt
ドキュメント
github.com/jwt/ruby-jwt#readme

互換性の概要

JOSE と JWT

JWS アルゴリズム
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

JWT.encode と JWT.decode は、Ruby の openssl gem の上で HS、RS、PS、ES の 256/384/512 と ES256K に対応します。EdDSA はもう jwt gem には含まれていません。jwt 3.3.0 だけを読み込んだ状態では EdDSA のエンコードが Unsupported signing method で失敗し、jwt-eddsa を追加すると動作しました。ただし、その依存関係であるネイティブの ed25519 はインストール時に C コンパイラーを必要とし、スリムなコンテナイメージにはコンパイラーが含まれていません。

検証にはアルゴリズムの指定が必須です。algorithm: を付けない JWT.decode(token, key, true) は Expected a different algorithm で失敗しました。署名のないトークンは検証時に拒否され、検証を無効にした場合(JWT.decode(token, nil, false))にしか読めませんが、信頼するトークンに対してコードでこれを行うことは決してすべきではありません。鍵の種類もチェックされます。EC 鍵の PEM テキストで偽造した HS256 トークンは、検証鍵が EC 鍵オブジェクトのときは HMAC key expected to be a String で失敗しました。しかし PEM 文字列を HS256 の鍵として渡すと検証を通ったため、鍵は OpenSSL::PKey オブジェクトのまま扱ってください。ES256 は署名時に P-384 鍵を拒否します。

クレームのチェックはオプトインです。時刻のずれは許容されず、exp は required_claims に列挙しない限り省略可能です。サンプルでは発行者と合わせて列挙しています。HMAC シークレットは長さを問わず受け入れられ、5 バイトの鍵でも警告なしで署名と検証ができたため、シークレットは JWT シークレットチェッカーで確認してください。

ES256 署名は 64 バイトの生の r‖s で、DER 署名は Signature verification failed で失敗します。トークンがどちらのエンコーディングかは、JWT デバッガーに貼り付けると確認できます。

事実とその根拠となるチェック

以下の内容はドキュメントからの転記ではありません。2026年10月20日に、チェック content/libraries/verify/ruby-jwt/run.sh がコンテナイメージ ruby:4.0.7-slim-trixie 内で ruby-jwt (jwt gem) 3.3.0 を実行しました。このページの内容はその出力と一致していなければならず、各事実には該当する出力行を示しています。

  • JWS アルゴリズム

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512ES256KEdDSA

    HS、RS、PS、ES の 256/384/512 と ES256K は jwt gem で署名・検証できました。EdDSA は jwt-eddsa gem を追加した後でのみ使えます。

    チェックによる記録
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, ES256K, EdDSA
    evidence jws-algorithms: EdDSA with only the jwt gem loaded: JWT::EncodeError: Unsupported signing method; after require "jwt/eddsa": ok
  • ES256 署名のエンコーディング

    raw

    ES256 署名は 64 バイトの生の r‖s です。

    チェックによる記録
    fact jws-ecdsa-encoding = raw
  • JWS 内の DER 署名

    rejected

    DER エンコードの ES256 署名は JWT::VerificationError: Signature verification failed で失敗します。

    チェックによる記録
    fact jws-der-signature = rejected
    evidence jws-der-signature: JWT::VerificationError: Signature verification failed
  • アルゴリズムの許可リスト

    required

    algorithm: なしで検証すると JWT::IncorrectAlgorithm: Expected a different algorithm で失敗します。

    チェックによる記録
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: JWT.decode with verify true and no algorithm option: JWT::IncorrectAlgorithm: Expected a different algorithm
  • 署名なしトークン(alg none)

    rejected

    署名のないトークンは Expected a different algorithm で検証に失敗し、読めるのは verify = false の場合だけです。

    チェックによる記録
    fact jwt-alg-none = rejected
    evidence jwt-alg-none: verify true, algorithm ES256: JWT::IncorrectAlgorithm: Expected a different algorithm; verify false: ok
  • HMAC シークレットとして使われる公開鍵

    rejected

    PEM テキストを鍵とした HS256 トークンは、EC 鍵オブジェクトに対しては HMAC key expected to be a String で失敗しましたが、PEM 文字列そのものを HS256 の鍵として渡すと検証を通りました。

    チェックによる記録
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: algorithms [ES256, HS256] with the EC key object: JWT::VerificationKeyError: HMAC key expected to be a String
    evidence jwt-hmac-public-key: algorithm HS256 with the PEM string as key: ok
  • alg と曲線の照合

    enforced

    P-384 鍵で ES256 の署名を行うと payload algorithm is ES256 but ES384 signing key was provided で失敗します。

    チェックによる記録
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: JWT::EncodeError: payload algorithm is ES256 but ES384 signing key was provided
  • デフォルトのクロックスキュー

    0s

    デフォルトでは時刻のずれを許容しません。3 秒前に期限切れになったトークンは拒否されました。

    チェックによる記録
    fact jwt-clock-skew = 0s
  • 有効期限クレームの要否

    no

    exp のないトークンもデコードできました。required_claims: ["exp"] を指定すると Missing required claim exp で失敗します。

    チェックによる記録
    fact jwt-exp-required = no
    evidence jwt-exp-required: with required_claims: ["exp"]: JWT::MissingRequiredClaim: Missing required claim exp
  • 短い HMAC シークレット

    accepted

    5 バイトの HS256 シークレットでも、警告なしで署名と検証ができました。

    チェックによる記録
    fact jwt-hmac-short-secret = accepted
    evidence jwt-hmac-short-secret: 5-byte HS256 key: ok

使い方

ES256 トークンをエンコードし、アルゴリズムを指定し、exp と iss を必須にして、30 秒の時刻のずれを許容してデコードします。
require "jwt"

key = OpenSSL::PKey::EC.generate("prime256v1")
public_key = OpenSSL::PKey.read(key.public_to_pem)
payload = { "sub" => "user-42", "iss" => "https://auth.example.com", "exp" => Time.now.to_i + 600 }
token = JWT.encode(payload, key, "ES256")

# Name the algorithm, require exp and iss, and allow some clock drift.
claims, header = JWT.decode(token, public_key, true,
                            algorithm: "ES256",
                            required_claims: %w[exp iss],
                            iss: "https://auth.example.com", verify_iss: true,
                            leeway: 30)
puts "sub: #{claims['sub']}, alg: #{header['alg']}"

スニペットのテストハーネスは、すべてのサンプルをコンテナイメージ ruby:4.0.7-slim-trixie 内でそのまま実行し、出力が一致しなければ実行は失敗します。

セットアップ
gem install --silent --no-document jwt -v 3.3.0
コマンド
ruby usage.rb
一致すべき出力(正規表現)
sub: user-42, alg: ES256

ライブラリ