Class: CF::UAA::TokenCoder
- Inherits:
-
Object
- Object
- CF::UAA::TokenCoder
- Defined in:
- lib/uaa/token_coder.rb
Overview
This class is for OAuth Resource Servers. Resource Servers get tokens and need to validate and decode them, but they do not obtain them from the Authorization Server. This class is for resource servers which accept bearer JWT tokens.
For more on JWT, see the JSON Web Token RFC here: http://tools.ietf.org/id/draft-ietf-oauth-json-web-token-05.html
An instance of this class can be used to decode and verify the contents of a bearer token. Methods of this class can validate token signatures with a secret or public key, and they can also enforce that the token is for a particular audience.
Class Method Summary collapse
-
.constant_time_compare(a, b) ⇒ boolean
Takes constant time to compare 2 strings (HMAC digests in this case) to avoid timing attacks while comparing the HMAC digests.
-
.decode(token, options = {}, obsolete1 = nil, obsolete2 = nil) ⇒ Hash
Decodes a JWT token and optionally verifies the signature.
-
.encode(token_body, options = {}, obsolete1 = nil, obsolete2 = nil) ⇒ String
Constructs a signed JWT.
Instance Method Summary collapse
-
#decode(auth_header) ⇒ Hash
Returns hash of values decoded from the token contents.
-
#decode_at_reference_time(auth_header, reference_time) ⇒ Hash
Returns hash of values decoded from the token contents, taking reference_time as the comparison time for expiration.
-
#encode(token_body = {}, algorithm = nil) ⇒ String
Encode a JWT token.
-
#initialize(options = {}, obsolete1 = nil, obsolete2 = nil) ⇒ TokenCoder
constructor
Creates a new token en/decoder for a service that is associated with the the audience_ids, the symmetrical token validation key, and the public and/or private keys.
Constructor Details
#initialize(options = {}, obsolete1 = nil, obsolete2 = nil) ⇒ TokenCoder
the TokenCoder instance must be configured with the appropriate key material to support particular algorithm families and operations – i.e. :pkey must include a private key in order to sign tokens with the RS algorithms.
Creates a new token en/decoder for a service that is associated with the the audience_ids, the symmetrical token validation key, and the public and/or private keys.
169 170 171 172 173 174 175 176 177 |
# File 'lib/uaa/token_coder.rb', line 169 def initialize( = {}, obsolete1 = nil, obsolete2 = nil) unless .is_a?(Hash) && obsolete1.nil? && obsolete2.nil? # deprecated: def initialize(audience_ids, skey, pkey = nil) warn "#{self.class}##{__method__} is deprecated with these parameters. Please use options hash." = {:audience_ids => } [:skey], [:pkey] = obsolete1, obsolete2 end @options = self.class.() end |
Class Method Details
.constant_time_compare(a, b) ⇒ boolean
Takes constant time to compare 2 strings (HMAC digests in this case) to avoid timing attacks while comparing the HMAC digests
131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/uaa/token_coder.rb', line 131 def self.constant_time_compare(a, b) if a.length != b.length return false end result = 0 a.chars.zip(b.chars).each do |x, y| result |= x.ord ^ y.ord end result == 0 end |
.decode(token, options = {}, obsolete1 = nil, obsolete2 = nil) ⇒ Hash
Decodes a JWT token and optionally verifies the signature. Both a symmetrical key and a public key can be provided for signature verification. The JWT header indicates what signature algorithm was used and the corresponding key is used to verify the signature (if verify is true).
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/uaa/token_coder.rb', line 95 def self.decode(token, = {}, obsolete1 = nil, obsolete2 = nil) unless .is_a?(Hash) && obsolete1.nil? && obsolete2.nil? # deprecated: def self.decode(token, skey = nil, pkey = nil, verify = true) warn "#{self.class}##{__method__} is deprecated with these parameters. Please use options hash." = {:skey => } [:pkey], [:verify] = obsolete1, obsolete2 end = () segments = token.split('.') raise InvalidTokenFormat, "Not enough or too many segments" unless [2,3].include? segments.length header_segment, payload_segment, crypto_segment = segments signing_input = [header_segment, payload_segment].join('.') header = Util.json_decode64(header_segment) payload = Util.json_decode64(payload_segment, (:sym if [:symbolize_keys])) return payload unless [:verify] raise SignatureNotAccepted, "Signature algorithm not accepted" unless [:accept_algorithms].include?(algo = header["alg"]) return payload if algo == 'none' signature = Util.decode64(crypto_segment) if ["HS256", "HS384", "HS512"].include?(algo) raise InvalidSignature, "Signature verification failed" unless [:skey] && constant_time_compare(signature, OpenSSL::HMAC.digest(init_digest(algo), [:skey], signing_input)) elsif ["RS256", "RS384", "RS512"].include?(algo) raise InvalidSignature, "Signature verification failed" unless [:pkey] && [:pkey].verify(init_digest(algo), signature, signing_input) else raise SignatureNotSupported, "Algorithm not supported" end payload end |
.encode(token_body, options = {}, obsolete1 = nil, obsolete2 = nil) ⇒ String
Constructs a signed JWT.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/uaa/token_coder.rb', line 64 def self.encode(token_body, = {}, obsolete1 = nil, obsolete2 = nil) unless .is_a?(Hash) && obsolete1.nil? && obsolete2.nil? # deprecated: def self.encode(token_body, skey, pkey = nil, algo = 'HS256') warn "#{self.class}##{__method__} is deprecated with these parameters. Please use options hash." = {:skey => } [:pkey], [:algorithm] = obsolete1, obsolete2 end = () algo = [:algorithm] segments = [Util.json_encode64("typ" => "JWT", "alg" => algo)] segments << Util.json_encode64(token_body) if ["HS256", "HS384", "HS512"].include?(algo) sig = OpenSSL::HMAC.digest(init_digest(algo), [:skey], segments.join('.')) elsif ["RS256", "RS384", "RS512"].include?(algo) sig = [:pkey].sign(init_digest(algo), segments.join('.')) elsif algo == "none" sig = "" else raise SignatureNotSupported, "unsupported signing method" end segments << Util.encode64(sig) segments.join('.') end |
Instance Method Details
#decode(auth_header) ⇒ Hash
Returns hash of values decoded from the token contents. If the audience_ids were specified in the options to this instance (see #initialize) and the token does not contain one or more of those audience_ids, an AuthError will be raised. AuthError is raised if the token has expired.
196 197 198 |
# File 'lib/uaa/token_coder.rb', line 196 def decode(auth_header) decode_at_reference_time(auth_header, Time.now.to_i) end |
#decode_at_reference_time(auth_header, reference_time) ⇒ Hash
Returns hash of values decoded from the token contents, taking reference_time as the comparison time for expiration. If the audience_ids were specified in the options to this instance (see #initialize) and the token does not contain one or more of those audience_ids, an AuthError will be raised. AuthError is raised if the token has expired.
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/uaa/token_coder.rb', line 208 def decode_at_reference_time(auth_header, reference_time) unless auth_header && (tkn = auth_header.split(' ')).length == 2 && tkn[0] =~ /^bearer$/i raise InvalidTokenFormat, "invalid authentication header: #{auth_header}" end reply = self.class.decode(tkn[1], @options) auds = Util.arglist(reply[:aud] || reply['aud']) if @options[:audience_ids] && (!auds || (auds & @options[:audience_ids]).empty?) raise InvalidAudience, "invalid audience: #{auds}" end exp = reply[:exp] || reply['exp'] unless exp.is_a?(Integer) && exp > reference_time raise TokenExpired, "token expired" end reply end |
#encode(token_body = {}, algorithm = nil) ⇒ String
Encode a JWT token. Takes a hash of values to use as the token body. Returns a signed token in JWT format (header, body, signature).
184 185 186 187 188 |
# File 'lib/uaa/token_coder.rb', line 184 def encode(token_body = {}, algorithm = nil) token_body[:aud] = @options[:audience_ids] if @options[:audience_ids] && !token_body[:aud] && !token_body['aud'] token_body[:exp] = Time.now.to_i + 7 * 24 * 60 * 60 unless token_body[:exp] || token_body['exp'] self.class.encode(token_body, algorithm ? @options.merge(:algorithm => algorithm) : @options) end |