Skip to content

Commit

Permalink
Test and documentation about the JWT::JWA::SigningAlgorithm module
Browse files Browse the repository at this point in the history
  • Loading branch information
anakinj committed Aug 27, 2024
1 parent 5766a73 commit c99e456
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 86 deletions.
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
77 changes: 2 additions & 75 deletions spec/integration/readme_examples_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
41 changes: 35 additions & 6 deletions spec/jwt/jwt_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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(*)
Expand All @@ -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

Expand All @@ -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')
Expand Down

0 comments on commit c99e456

Please sign in to comment.