Module: JWT

Defined in:
lib/ruby_jwt.rb

Defined Under Namespace

Classes: DecodeResponse, SignError, VerificationError

Constant Summary collapse

SIGNATURES =

class OpenSSL::PKey::EC alias_method :private?, :private_key? end

{"256" => OpenSSL::Digest::SHA256.new(), "384" => OpenSSL::Digest::SHA384.new(), "512" => OpenSSL::Digest::SHA512.new()}

Class Method Summary collapse

Class Method Details

.base64urldecode(val) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/ruby_jwt.rb', line 158

def base64urldecode(val)
	begin
		case(val.length % 4)
		when 0 
			return Base64.urlsafe_decode64(val)
		when 2
			return Base64.urlsafe_decode64("#{val}==")
		when 3
			return Base64.urlsafe_decode64("#{val}=")
		else
			raise JWT::DecodeError.new("Illegal base64 string!")
		end
	rescue ArgumentError => e
		raise JWT::VerificationError.new(e.message)
	end

end

.base64urlencode(val) ⇒ Object



154
155
156
# File 'lib/ruby_jwt.rb', line 154

def base64urlencode(val)
	return Base64.urlsafe_encode64(val).gsub("=","")
end

.decode(token) ⇒ Object



58
59
60
61
62
63
# File 'lib/ruby_jwt.rb', line 58

def decode(token)
	jwt_parts = token.split(".")
	header = json_decode_data(jwt_parts[0])
	payload = json_decode_data(jwt_parts[1])
	return DecodeResponse.new(header,payload,jwt_parts[2])
end

.encode_header(header_options) ⇒ Object



113
114
115
116
# File 'lib/ruby_jwt.rb', line 113

def encode_header(header_options)
	header  = {:typ => "JWT"}.merge(header_options)
	return base64urlencode(JSON.dump(header))
end

.encode_payload(payload) ⇒ Object



118
119
120
# File 'lib/ruby_jwt.rb', line 118

def encode_payload(payload)
	return base64urlencode(JSON.dump(payload))
end

.encode_signature(data, key, alg) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/ruby_jwt.rb', line 122

def encode_signature(data,key,alg)
	case alg
	when "none"
		return ""
	when "HS256","HS384", "HS512"
		return base64urlencode(OpenSSL::HMAC.digest(SIGNATURES[alg.gsub("HS","")], key, data))
	when "RS256", "RS384", "RS512"
		return base64urlencode(key.sign(SIGNATURES[alg.gsub("RS","")],data))
	when "ES256", "ES384", "ES512"
		return base64urlencode(key.dsa_sign_asn1(SIGNATURES[alg.gsub("ES","")].digest(data)))
		#return base64urlencode(key.sign(SIGNATURES[alg.gsub("ES","")],data))
	else
		raise JWT::SignError.new("Unsupported signing method!")
	end
end

.json_decode_data(data) ⇒ Object

utility methods



105
106
107
108
109
110
111
# File 'lib/ruby_jwt.rb', line 105

def json_decode_data(data)
	if defined?(Rails)
		return JSON.load(base64urldecode(data)).symbolize_keys!
	else
		return symbolize_keys(JSON.load(base64urldecode(data)))
	end
end

.sign(payload, key, payload_options, header_options) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/ruby_jwt.rb', line 35

def sign(payload,key,payload_options,header_options)
	jwt_parts = []
	header_options = header_options || {}
	payload_options = payload_options || {}
	header_options[:alg] = header_options[:alg] || "HS256"
	if(header_options[:alg] != "none" and (!key))
		raise JWT::SignError.new("Key cannot be blank if algorithm is not 'none'")
	end
	payload[:iat] = Time.now.to_i
	if(payload_options[:exp])
		payload_options[:exp] += payload[:iat] 
	end

	if(payload_options[:nbf])
		payload_options[:nbf] += payload[:iat]
	end
	payload.merge!(payload_options)
	jwt_parts << encode_header(header_options)
	jwt_parts << encode_payload(payload)
	jwt_parts << encode_signature(jwt_parts.join("."),key, header_options[:alg])
	return jwt_parts.join(".")
end

.symbolize_keys(hash) ⇒ Object



176
177
178
# File 'lib/ruby_jwt.rb', line 176

def symbolize_keys(hash)
	return hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
end

.time_compare(a, b) ⇒ Object



180
181
182
183
184
185
186
# File 'lib/ruby_jwt.rb', line 180

def time_compare(a,b)
	return false if a.nil? || b.nil? || a.empty? || b.empty? || a.bytesize != b.bytesize
	l = a.bytes
  compare = 0
	b.bytes.each {|byte| compare += byte ^ l.shift}
		return compare == 0
end

.verify(token, secret, options = {}) ⇒ Object

Raises:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/ruby_jwt.rb', line 65

def verify(token,secret,options={})
	raise VerificationError.new("JWT cannot be blank") if !token or token.empty?
	jwt_parts = token.split(".")
	jwt = decode(token)
	alg = jwt.header[:alg]
	
	raise VerificationError.new("JWT has invalid number of segments.") if(jwt_parts.count < 3 and alg != "none")
	raise VerificationError.new("JWT has invalid number of segments.") if(jwt_parts.count < 2 and alg == "none")

	payload = jwt.payload
	signature = jwt.signature.nil? ? "none" : base64urldecode(jwt.signature)

	raise VerificationError.new("JWT signature is required.") if(jwt.signature.nil? and !secret.nil?) 
	current_time = Time.now.to_i
	if(payload[:exp] and current_time >= payload[:exp])
		raise VerificationError.new("JWT is expired.")
	end

	if(payload[:nbf] and current_time < payload[:nbf])
		raise VerificationError.new( "JWT nbf has not passed yet.")
	end

	if(options[:iss])
		raise VerificationError.new("JWT issuer is invalid.") if options[:iss] != payload[:iss]
	end

	if(options[:aud])
		audience = (options[:aud].is_a? Array) ? options[:aud] : [options[:aud]]
		raise VerificationError.new("JWT audience is invalid.") if !audience.include? payload[:aud]
	end

	raise VerificationError.new("JWT signature is invalid.") if !verify_signature(alg,secret,jwt_parts[0..1].join("."),signature)

	return jwt
end

.verify_signature(alg, key, data, signature) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/ruby_jwt.rb', line 138

def verify_signature(alg,key,data,signature)
	case alg
	when "none"
		return true
	when "HS256","HS384", "HS512"
		return time_compare(signature,OpenSSL::HMAC.digest(SIGNATURES[alg.gsub("HS","")], key, data))
	when "RS256", "RS384", "RS512"
		return key.verify(SIGNATURES[alg.gsub("RS","")],signature, data)
	when "ES256", "ES384", "ES512"
		return key.dsa_verify_asn1(SIGNATURES[alg.gsub("ES","")].digest(data),signature)
		#return key.verify(SIGNATURES[alg.gsub("ES","")],signature, data)
	else
		raise JWT::VerificationError.new("Unsupported signing method!")
	end
end