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
-
.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.
- .init_digest(algo) ⇒ Object
- .normalize_options(opts) ⇒ Object
Instance Method Summary collapse
-
#decode(auth_header) ⇒ Hash
Returns hash of values decoded from the token contents.
-
#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.
151 152 153 154 155 156 157 158 159 |
# File 'lib/uaa/token_coder.rb', line 151 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
.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] && 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 |
.init_digest(algo) ⇒ Object
44 45 46 |
# File 'lib/uaa/token_coder.rb', line 44 def self.init_digest(algo) # @private OpenSSL::Digest::Digest.new(algo.sub('HS', 'sha').sub('RS', 'sha')) end |
.normalize_options(opts) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/uaa/token_coder.rb', line 48 def self.(opts) # @private opts = opts.dup pk = opts[:pkey] opts[:pkey] = OpenSSL::PKey::RSA.new(pk) if pk && !pk.is_a?(OpenSSL::PKey::PKey) opts[:audience_ids] = Util.arglist(opts[:audience_ids]) opts[:algorithm] = 'HS256' unless opts[:algorithm] opts[:verify] = true unless opts.key?(:verify) opts[:accept_algorithms] = Util.arglist(opts[:accept_algorithms], ["HS256", "HS384", "HS512", "RS256", "RS384", "RS512"]) opts 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.
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/uaa/token_coder.rb', line 178 def decode(auth_header) 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 > Time.now.to_i 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).
166 167 168 169 170 |
# File 'lib/uaa/token_coder.rb', line 166 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 |