Skip to content

Commit

Permalink
Merge pull request #37 from danny8376/dynamic_key
Browse files Browse the repository at this point in the history
Support for dynamic key & auto selecting algorithm by JWT header
  • Loading branch information
stakach authored Aug 30, 2021
2 parents a71df76 + 14b5f3f commit 433681e
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 4 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ payload, header = JWT.decode(token, "$secretKey", JWT::Algorithm::HS256)
payload, header = JWT.decode(token, verify: false, validate: false)
# Verification checks the signature
# Validation is checking if the token has expired etc

# You may also dynamically decide the key by passing a block to the decode function
# algorithm is optionally, you can omit it to use algorithm defined in the header
payload, header = JWT.decode(token, JWT::Algorithm::HS256) do |header, payload|
"the key"
end
```

## Supported algorithms
Expand Down
18 changes: 18 additions & 0 deletions spec/jwt_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ describe JWT do
header.should eq({"typ" => "JWT", "alg" => "HS256"})
payload.should eq({"k1" => "v1", "k2" => "v2"})
end

it "decodes and verifies JWT with dynamic key" do
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrMSI6InYxIiwiazIiOiJ2MiJ9.spzfy63YQSKdoM3av9HHvLtWzFjPd1hbch2g3T1-nu4"
payload, header = JWT.decode(token, algorithm: JWT::Algorithm::HS256) do |header, payload|
"SecretKey"
end
header.should eq({"typ" => "JWT", "alg" => "HS256"})
payload.should eq({"k1" => "v1", "k2" => "v2"})
end

it "decodes and verifies JWT with dynamic key and auto algorithm" do
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrMSI6InYxIiwiazIiOiJ2MiJ9.spzfy63YQSKdoM3av9HHvLtWzFjPd1hbch2g3T1-nu4"
payload, header = JWT.decode(token) do |header, payload|
"SecretKey"
end
header.should eq({"typ" => "JWT", "alg" => "HS256"})
payload.should eq({"k1" => "v1", "k2" => "v2"})
end
end

describe "#encode_header" do
Expand Down
44 changes: 40 additions & 4 deletions src/jwt.cr
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,63 @@ module JWT
def decode(token : String, key : String = "", algorithm : Algorithm = Algorithm::None, verify = true, validate = true, **options) : Tuple
verify_data, _, encoded_signature = token.rpartition('.')

check_verify_data(verify_data)

verify(key, algorithm, verify_data, encoded_signature) if verify

header, payload = decode_verify_data(verify_data)
validate(payload, options) if validate

{payload, header}
rescue error : TypeCastError
raise DecodeError.new("Invalid JWT payload", error)
end

def decode(token : String, algorithm : Algorithm? = nil, verify = true, validate = true, **options, &block) : Tuple
verify_data, _, encoded_signature = token.rpartition('.')

check_verify_data(verify_data)
header, payload = decode_verify_data(verify_data)

if algorithm.nil?
begin
algorithm = Algorithm.parse header["alg"].as_s
rescue error : ArgumentError | KeyError
raise DecodeError.new("Invalid alg in JWT header", error)
end
end
key = yield header, payload

verify(key.not_nil!, algorithm.not_nil!, verify_data, encoded_signature) if verify
validate(payload, options) if validate

{payload, header}
rescue error : TypeCastError
raise DecodeError.new("Invalid JWT payload", error)
end

private def check_verify_data(verify_data)
count = verify_data.count('.')
if count != 1
raise DecodeError.new("Invalid number of segments in the token. Expected 3 got #{count + 2}")
end
verify(key, algorithm, verify_data, encoded_signature) if verify
end

private def decode_verify_data(verify_data)
encoded_header, encoded_payload = verify_data.split('.')
header_json = Base64.decode_string(encoded_header)
header = JSON.parse(header_json).as_h

payload_json = Base64.decode_string(encoded_payload)
payload = JSON.parse(payload_json)
validate(payload, options) if validate

{payload, header}
{ header, payload }
rescue error : Base64::Error
raise DecodeError.new("Invalid Base64", error)
rescue error : JSON::ParseException
raise DecodeError.new("Invalid JSON", error)
rescue error : TypeCastError
raise DecodeError.new("Invalid JWT payload", error)
raise DecodeError.new("Invalid JWT header", error)
end

# public key verification for RSA and ECDSA algorithms
Expand Down

0 comments on commit 433681e

Please sign in to comment.