-
Notifications
You must be signed in to change notification settings - Fork 376
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #117 from excpt/master
Refactor decode and verify functionality
- Loading branch information
Showing
4 changed files
with
140 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
require 'jwt/json' | ||
require 'jwt/verify' | ||
|
||
# JWT::Decode module | ||
module JWT | ||
extend JWT::Json | ||
|
||
# Decoding logic for JWT | ||
class Decode | ||
attr_reader :header, :payload, :signature | ||
|
||
def initialize(jwt, key, verify, options, &keyfinder) | ||
@jwt = jwt | ||
@key = key | ||
@verify = verify | ||
@options = options | ||
@keyfinder = keyfinder | ||
end | ||
|
||
def decode_segments | ||
header_segment, payload_segment, crypto_segment = raw_segments(@jwt, @verify) | ||
@header, @payload = decode_header_and_payload(header_segment, payload_segment) | ||
@signature = base64url_decode(crypto_segment.to_s) if @verify | ||
signing_input = [header_segment, payload_segment].join('.') | ||
[@header, @payload, @signature, signing_input] | ||
end | ||
|
||
def raw_segments(jwt, verify) | ||
segments = jwt.split('.') | ||
required_num_segments = verify ? [3] : [2, 3] | ||
fail(JWT::DecodeError, 'Not enough or too many segments') unless required_num_segments.include? segments.length | ||
segments | ||
end | ||
private :raw_segments | ||
|
||
def decode_header_and_payload(header_segment, payload_segment) | ||
header = JWT.decode_json(base64url_decode(header_segment)) | ||
payload = JWT.decode_json(base64url_decode(payload_segment)) | ||
[header, payload] | ||
end | ||
private :decode_header_and_payload | ||
|
||
def base64url_decode(str) | ||
str += '=' * (4 - str.length.modulo(4)) | ||
Base64.decode64(str.tr('-_', '+/')) | ||
end | ||
private :base64url_decode | ||
|
||
def verify | ||
@options.each do |key, val| | ||
next unless key.to_s.match(/verify/) | ||
|
||
Verify.send(key, payload, @options) if val | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
module JWT | ||
# JWT verify methods | ||
module Verify | ||
def self.verify_expiration(payload, options) | ||
return unless payload.include?('exp') | ||
|
||
if payload['exp'].to_i < (Time.now.to_i - options[:leeway]) | ||
fail(JWT::ExpiredSignature, 'Signature has expired') | ||
end | ||
end | ||
|
||
def self.verify_not_before(payload, options) | ||
return unless payload.include?('nbf') | ||
|
||
if payload['nbf'].to_i > (Time.now.to_i + options[:leeway]) | ||
fail(JWT::ImmatureSignature, 'Signature nbf has not been reached') | ||
end | ||
end | ||
|
||
def self.verify_iss(payload, options) | ||
return unless options[:iss] | ||
|
||
if payload['iss'].to_s != options[:iss].to_s | ||
fail( | ||
JWT::InvalidIssuerError, | ||
"Invalid issuer. Expected #{options[:iss]}, received #{payload['iss'] || '<none>'}" | ||
) | ||
end | ||
end | ||
|
||
def self.verify_iat(payload, options) | ||
return unless payload.include?('iat') | ||
|
||
if !(payload['iat'].is_a?(Integer)) || payload['iat'].to_i > (Time.now.to_i + options[:leeway]) | ||
fail(JWT::InvalidIatError, 'Invalid iat') | ||
end | ||
end | ||
|
||
def self.verify_jti(payload, _options) | ||
fail(JWT::InvalidJtiError, 'Missing jti') if payload['jti'].to_s == '' | ||
end | ||
|
||
def self.verify_aud(payload, options) | ||
return unless options[:aud] | ||
|
||
if payload[:aud].is_a?(Array) | ||
fail( | ||
JWT::InvalidAudError, | ||
'Invalid audience' | ||
) unless payload['aud'].include?(options[:aud].to_s) | ||
else | ||
fail( | ||
JWT::InvalidAudError, | ||
"Invalid audience. Expected #{options[:aud]}, received #{payload['aud'] || '<none>'}" | ||
) unless payload['aud'].to_s == options[:aud].to_s | ||
end | ||
end | ||
|
||
def self.verify_sub(payload, options) | ||
return unless options[:sub] | ||
|
||
|
||
fail( | ||
JWT::InvalidSubError, | ||
"Invalid subject. Expected #{options[:sub]}, received #{payload['sub'] || '<none>'}" | ||
) unless payload['sub'].to_s == options[:sub].to_s | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
require 'spec_helper' | ||
require 'jwt' | ||
require 'jwt/decode' | ||
|
||
describe JWT do | ||
let(:payload) { { 'user_id' => '[email protected]' } } | ||
|
@@ -340,8 +341,8 @@ | |
end | ||
|
||
let :invalid_token do | ||
new_payload = payload.merge('sub' => 'we are not the druids you are looking for') | ||
JWT.encode new_payload, data[:secret] | ||
invalid_payload = payload.merge('sub' => 'we are not the druids you are looking for') | ||
JWT.encode invalid_payload, data[:secret] | ||
end | ||
|
||
it 'invalid sub should raise JWT::InvalidSubError' do | ||
|