gem install jwt -v 3.3.0 # or in a Gemfile: gem "jwt", "3.3.0" require "jwt"require "openssl" public_key = OpenSSL::PKey.read(File.read("pub.pem"))token = File.read("token.txt").strip payload, header = JWT.decode( token, public_key, true, # true: verify the signature and the claims algorithm: "ES256", # the only algorithm accepted iss: "https://ecdsa.com", verify_iss: true, required_claims: %w[exp sub], # exp is checked only when present otherwise leeway: 30 # seconds of clock skew for exp and nbf)puts "valid #{header['alg']} token for #{payload['sub']}, expires #{Time.at(payload['exp']).utc}" How it works
OpenSSL::PKey.readloads the SPKI public key; ruby-jwt accepts the resultingOpenSSL::PKey::ECdirectly as the verification key.- The third argument
trueenables verification, andalgorithm: "ES256"pins the algorithm — a token whose header saysHS256raisesJWT::IncorrectAlgorithm. iss:together withverify_iss: truecompares the issuer;required_claimsmakesexpandsubmandatory;leewayallows clock skew forexpandnbf.decodereturns[payload, header]as plain hashes, or raises aJWT::DecodeErrorsubclass — the run triggersVerificationError,IncorrectAlgorithm,ExpiredSignatureandInvalidIssuerError.
Gotchas
iss: "https://ecdsa.com"on its own does nothing: withoutverify_iss: truea token from any issuer is accepted. The same pattern applies toaud:andverify_aud.- A token without
expis accepted unlessexpis listed inrequired_claims— expiry is only enforced when the claim exists. JWT.decode(token, nil, false)returns the payload without checking anything; it is for debugging only, never for authorization.- Keys from an identity provider rotate: pass
jwks:(aJWT::JWK::Setor a loader lambda) instead of a fixed key, so the token'skidselects the current key.