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.
-
.decode_token_expiry(token) ⇒ Integer
Decodes a JWT token to extract its expiry time.
-
.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.
186 187 188 189 190 191 192 193 194 |
# File 'lib/uaa/token_coder.rb', line 186 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
148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/uaa/token_coder.rb', line 148 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 125 126 127 128 129 130 |
# 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 "WARNING: #{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])) unless [:verify] warn "WARNING: Decoding token without verifying it was signed by its authoring UAA" return payload end raise SignatureNotAccepted, "Signature algorithm not accepted" unless [:accept_algorithms].include?(algo = header["alg"]) if algo == 'none' warn "WARNING: Decoding token that explicitly states it has not been signed by an authoring UAA" return payload end 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 |
.decode_token_expiry(token) ⇒ Integer
Decodes a JWT token to extract its expiry time
135 136 137 138 139 140 141 |
# File 'lib/uaa/token_coder.rb', line 135 def self.decode_token_expiry(token) segments = token.split('.') raise InvalidTokenFormat, "Not enough or too many segments" unless [2,3].include? segments.length header_segment, payload_segment, crypto_segment = segments payload = Util.json_decode64(payload_segment, :sym) payload[:exp] 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 "WARNING: #{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.
213 214 215 |
# File 'lib/uaa/token_coder.rb', line 213 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.
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/uaa/token_coder.rb', line 225 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).
201 202 203 204 205 |
# File 'lib/uaa/token_coder.rb', line 201 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 |