From 1f5d7450022579600294ab775e3d78b83ee4132e Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Sat, 28 Dec 2024 11:59:55 +0200 Subject: [PATCH] JWT::EncodedToken#verify! to bundle signature and claim validation --- CHANGELOG.md | 1 + README.md | 10 ++++++++++ lib/jwt/claims/verification_methods.rb | 11 ++++++++++- lib/jwt/encoded_token.rb | 16 ++++++++++++++++ spec/jwt/encoded_token_spec.rb | 25 +++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74fc44a1..f9bc7541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ **Features:** +- JWT::EncodedToken#verify! method that bundles signature and claim validation [#647](https://github.com/jwt/ruby-jwt/pull/647) ([@anakinj](https://github.com/anakinj)) - Your contribution here **Fixes and enhancements:** diff --git a/README.md b/README.md index 7009d6b8..49b25e86 100644 --- a/README.md +++ b/README.md @@ -335,6 +335,16 @@ encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' } encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'} ``` +A simple way to handle token verification using the `JWT::EncodedToken#verify!` method. It verifies the signature and the `exp` claim by default. + +```ruby +encoded_token = JWT::EncodedToken.new(token.jwt) +encoded_token.verify!(signature: {algorithm: 'HS256', key: "secret"}) + +encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' } +encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'} +``` + ### Detached payload The `::JWT::Token#detach_payload!` method can be use to detach the payload from the JWT. diff --git a/lib/jwt/claims/verification_methods.rb b/lib/jwt/claims/verification_methods.rb index 534d3369..4c8ec0a8 100644 --- a/lib/jwt/claims/verification_methods.rb +++ b/lib/jwt/claims/verification_methods.rb @@ -2,16 +2,25 @@ module JWT module Claims - # @api private + # Provides methods to verify the claims of a token. module VerificationMethods + # Verifies the claims of the token. + # @param options [Array, Hash] the claims to verify. + # @raise [JWT::DecodeError] if the claims are invalid. def verify_claims!(*options) Verifier.verify!(self, *options) end + # Returns the errors of the claims of the token. + # @param options [Array, Hash] the claims to verify. + # @return [Array] the errors of the claims. def claim_errors(*options) Verifier.errors(self, *options) end + # Returns whether the claims of the token are valid. + # @param options [Array, Hash] the claims to verify. + # @return [Boolean] whether the claims are valid. def valid_claims?(*options) claim_errors(*options).empty? end diff --git a/lib/jwt/encoded_token.rb b/lib/jwt/encoded_token.rb index f888d1a7..ac608f3d 100644 --- a/lib/jwt/encoded_token.rb +++ b/lib/jwt/encoded_token.rb @@ -72,6 +72,22 @@ def signing_input [encoded_header, encoded_payload].join('.') end + # Verifies the token signature and claims. + # By default it verifies the 'exp' claim. + # + # @example + # encoded_token.verify!(signature: { algorithm: 'HS256', key: 'secret' }, claims: [:exp]) + # + # @param signature [Hash] the parameters for signature verification (see {#verify_signature!}). + # @param claims [Array, Hash] the claims to verify (see {#verify_claims!}). + # @return [nil] + # @raise [JWT::DecodeError] if the signature or claim verification fails. + def verify!(signature:, claims: [:exp]) + verify_signature!(**signature) + claims.is_a?(Array) ? verify_claims!(*claims) : verify_claims!(claims) + nil + end + # Verifies the signature of the JWT token. # # @param algorithm [String, Array, Object, Array] the algorithm(s) to use for verification. diff --git a/spec/jwt/encoded_token_spec.rb b/spec/jwt/encoded_token_spec.rb index 522fe270..037a8127 100644 --- a/spec/jwt/encoded_token_spec.rb +++ b/spec/jwt/encoded_token_spec.rb @@ -207,6 +207,31 @@ end end + context '#verify!' do + context 'when key is valid' do + it 'does not raise' do + expect(token.verify!(signature: { algorithm: 'HS256', key: 'secret' })).to eq(nil) + end + end + + context 'when key is invalid' do + it 'raises an error' do + expect { token.verify!(signature: { algorithm: 'HS256', key: 'wrong' }) }.to raise_error(JWT::VerificationError, 'Signature verification failed') + end + end + + context 'when claims are invalid' do + let(:payload) { { 'pay' => 'load', exp: Time.now.to_i - 1000 } } + + it 'raises an error' do + expect do + token.verify!(signature: { algorithm: 'HS256', key: 'secret' }, + claims: { exp: { leeway: 900 } }) + end.to raise_error(JWT::ExpiredSignature, 'Signature has expired') + end + end + end + describe '#valid_claims?' do context 'exp claim' do let(:payload) { { 'exp' => Time.now.to_i - 10, 'pay' => 'load' } }