diff --git a/README.md b/README.md index 41beb8f1..97a47a5a 100644 --- a/README.md +++ b/README.md @@ -223,18 +223,22 @@ puts decoded_token ### **Custom algorithms** -An object implementing custom signing or verification behaviour can be passed in the `algorithm` option when encoding and decoding. The given object needs to implement the method `valid_alg?` and `verify` and/or `alg` and `sign`, depending if object is used for encoding or decoding. +An object implementing custom signing or verification behaviour can be passed in the `algorithm` option when encoding and decoding. The given object needs to include or extend the `JWT::JWA::SigningAlgorithm` module and implement the method `valid_alg?` and `verify` and/or `alg` and `sign`, depending if object is used for encoding or decoding. + +When encoding or decoding a token, you can pass in a custom object through the `algorithm` option to handle signing or verification. This custom object must include or extend the `JWT::JWA::SigningAlgorithm` module and implement certain methods: + +- For decoding/verifying: The object must implement the methods `alg` and `verify`. +- For encoding/signing: The object must implement the methods `alg` and `sign`. + ```ruby module CustomHS512Algorithm + extend JWT::JWA::SigningAlgorithm + def self.alg 'HS512' end - def self.valid_alg?(alg_to_validate) - alg_to_validate == alg - end - def self.sign(data:, signing_key:) OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), data, signing_key) end diff --git a/spec/integration/readme_examples_spec.rb b/spec/integration/readme_examples_spec.rb index a93f5846..ff488a96 100644 --- a/spec/integration/readme_examples_spec.rb +++ b/spec/integration/readme_examples_spec.rb @@ -451,14 +451,12 @@ context 'custom algorithm example' do it 'allows a module to be used as algorithm on encode and decode' do custom_hs512_alg = Module.new do + extend JWT::JWA::SigningAlgorithm + def self.alg 'HS512' end - def self.valid_alg?(alg_to_validate) - alg_to_validate == alg - end - def self.sign(data:, signing_key:) OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), data, signing_key) end @@ -468,80 +466,9 @@ def self.verify(data:, signature:, verification_key:) end end - expect(JWT::Deprecations).to receive(:warning).and_call_original - token = JWT.encode({ 'pay' => 'load' }, 'secret', custom_hs512_alg) _payload, header = JWT.decode(token, 'secret', true, algorithm: custom_hs512_alg) - expect(header).to include('alg' => 'HS512') end - - context 'with custom header' do - it 'adds the custom header' do - custom_hs512_alg = Module.new do - def self.header(*) - { 'alg' => alg, 'foo' => 'bar' } - end - - def self.alg - 'HS512' - end - - def self.valid_alg?(alg_to_validate) - alg_to_validate == alg - end - - def self.sign(data:, signing_key:) - OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), data, signing_key) - end - - def self.verify(data:, signature:, verification_key:) - sign(data: data, signing_key: verification_key) == signature - end - end - - expect(JWT::Deprecations).to receive(:warning).and_call_original - - token = JWT.encode({ 'pay' => 'load' }, 'secret', custom_hs512_alg) - _payload, header = JWT.decode(token, 'secret', true, algorithm: custom_hs512_alg) - - expect(header).to include('foo' => 'bar') - end - end - - context 'whem including SigningAlgorithm' do - it 'adds the custom header' do - custom_hs512_alg = Module.new do - extend JWT::JWA::SigningAlgorithm - - def self.header(*) - { 'alg' => alg, 'foo' => 'bar' } - end - - def self.alg - 'HS512' - end - - def self.valid_alg?(alg_to_validate) - alg_to_validate == alg - end - - def self.sign(data:, signing_key:) - OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), data, signing_key) - end - - def self.verify(data:, signature:, verification_key:) - sign(data: data, signing_key: verification_key) == signature - end - end - - expect(JWT::Deprecations).not_to receive(:warning) - - token = JWT.encode({ 'pay' => 'load' }, 'secret', custom_hs512_alg) - _payload, header = JWT.decode(token, 'secret', true, algorithm: custom_hs512_alg) - - expect(header).to include('foo' => 'bar') - end - end end end diff --git a/spec/jwt/jwt_spec.rb b/spec/jwt/jwt_spec.rb index ddd68ecd..69c1965d 100644 --- a/spec/jwt/jwt_spec.rb +++ b/spec/jwt/jwt_spec.rb @@ -851,11 +851,11 @@ context 'when algorithm is a custom class' do let(:custom_algorithm) do Class.new do - attr_reader :alg + include JWT::JWA::SigningAlgorithm def initialize(signature: 'custom_signature', alg: 'custom') @signature = signature - @alg = alg + @alg = alg end def sign(*) @@ -865,10 +865,6 @@ def sign(*) def verify(data:, signature:, verification_key:) # rubocop:disable Lint/UnusedMethodArgument signature == @signature end - - def valid_alg?(alg) - alg == self.alg - end end end @@ -889,6 +885,39 @@ def valid_alg?(alg) end end + context 'when class has custom header method' do + before do + custom_algorithm.class_eval do + def header(*) + { 'alg' => alg, 'foo' => 'bar' } + end + end + end + + it 'uses the provided header' do + expect(JWT.decode(token, 'secret', true, algorithm: custom_algorithm.new)).to eq([payload, { 'alg' => 'custom', 'foo' => 'bar' }]) + end + end + + context 'when class is not utilizing the ::JWT::JWA::SigningAlgorithm module' do + let(:custom_algorithm) do + Class.new do + def initialize(signature: 'custom_signature', alg: 'custom') + @signature = signature + @alg = alg + end + + def sign(*) + @signature + end + end + end + + it 'emits a deprecation warning' do + expect { token }.to output("[DEPRECATION WARNING] Custom algorithms are required to include JWT::JWA::SigningAlgorithm\n").to_stderr + end + end + context 'when alg is not matching' do it 'fails the validation process' do expect { JWT.decode(token, 'secret', true, algorithms: custom_algorithm.new(alg: 'not_a_match')) }.to raise_error(JWT::IncorrectAlgorithm, 'Expected a different algorithm')