Class: CF::UAA::TokenCoder

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initialize(options = {}, obsolete1 = nil, obsolete2 = nil) ⇒ TokenCoder

Note:

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.

Parameters:

  • options (Hash) (defaults to: {})

    Supported options:

    • :audience_ids [Array<String>, String] – An array or space separated string of values which indicate the token is intended for this service instance. It will be compared with tokens as they are decoded to ensure that the token was intended for this audience.

    • :skey [String] – used to sign and validate tokens using symmetrical key algoruthms

    • :pkey [String, File, OpenSSL::PKey::PKey] – may be a String or File in PEM or DER formats. May include public and/or private key data. The private key is used to sign tokens and the public key is used to validate tokens.

    • :algorithm [String] – Sets default used for encoding. May be HS256, HS384, HS512, RS256, RS384, RS512, or none.

    • :verify [String] – Verifies signatures when decoding tokens. Defaults to true.

    • :accept_algorithms [String, Array<String>] – An Array or space separated string of values which list what algorthms are accepted for token signatures. Defaults to all possible values of :algorithm except ‘none’.



140
141
142
143
144
145
146
147
148
# File 'lib/uaa/token_coder.rb', line 140

def initialize(options = {}, obsolete1 = nil, obsolete2 = nil)
  unless options.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."
    options = {:audience_ids => options }
    options[:skey], options[:pkey] = obsolete1, obsolete2
  end
  @options = self.class.normalize_options(options)
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).

Parameters:

  • token (String)

    A JWT token as returned by encode

  • options (Hash) (defaults to: {})

    Supported options:

    • :audience_ids [Array<String>, String] – An array or space separated string of values which indicate the token is intended for this service instance. It will be compared with tokens as they are decoded to ensure that the token was intended for this audience.

    • :skey [String] – used to sign and validate tokens using symmetrical key algoruthms

    • :pkey [String, File, OpenSSL::PKey::PKey] – may be a String or File in PEM or DER formats. May include public and/or private key data. The private key is used to sign tokens and the public key is used to validate tokens.

    • :algorithm [String] – Sets default used for encoding. May be HS256, HS384, HS512, RS256, RS384, RS512, or none.

    • :verify [String] – Verifies signatures when decoding tokens. Defaults to true.

    • :accept_algorithms [String, Array<String>] – An Array or space separated string of values which list what algorthms are accepted for token signatures. Defaults to all possible values of :algorithm except ‘none’.

Returns:

  • (Hash)

    the token contents

Raises:



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/uaa/token_coder.rb', line 84

def self.decode(token, options = {}, obsolete1 = nil, obsolete2 = nil)
  unless options.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."
    options = {:skey => options }
    options[:pkey], options[:verify] = obsolete1, obsolete2
  end
  options = normalize_options(options)
  segments = token.split('.')
  raise DecodeError, "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 options[:symbolize_keys]))
  return payload unless options[:verify]
  raise DecodeError, "Signature algorithm not accepted" unless
      options[:accept_algorithms].include?(algo = header["alg"])
  return payload if algo == 'none'
  signature = Util.decode64(crypto_segment)
  if ["HS256", "HS384", "HS512"].include?(algo)
    raise DecodeError, "Signature verification failed" unless
        signature == OpenSSL::HMAC.digest(init_digest(algo), options[:skey], signing_input)
  elsif ["RS256", "RS384", "RS512"].include?(algo)
    raise DecodeError, "Signature verification failed" unless
        options[:pkey].verify(init_digest(algo), signature, signing_input)
  else
    raise DecodeError, "Algorithm not supported"
  end
  payload
end

.encode(token_body, options = {}, obsolete1 = nil, obsolete2 = nil) ⇒ String

Constructs a signed JWT.

Parameters:

  • token_body

    Contents of the token in any object that can be converted to JSON.

  • options (Hash) (defaults to: {})

    Supported options:

    • :audience_ids [Array<String>, String] – An array or space separated string of values which indicate the token is intended for this service instance. It will be compared with tokens as they are decoded to ensure that the token was intended for this audience.

    • :skey [String] – used to sign and validate tokens using symmetrical key algoruthms

    • :pkey [String, File, OpenSSL::PKey::PKey] – may be a String or File in PEM or DER formats. May include public and/or private key data. The private key is used to sign tokens and the public key is used to validate tokens.

    • :algorithm [String] – Sets default used for encoding. May be HS256, HS384, HS512, RS256, RS384, RS512, or none.

    • :verify [String] – Verifies signatures when decoding tokens. Defaults to true.

    • :accept_algorithms [String, Array<String>] – An Array or space separated string of values which list what algorthms are accepted for token signatures. Defaults to all possible values of :algorithm except ‘none’.

Returns:

  • (String)

    a signed JWT token string in the form “xxxx.xxxxx.xxxx”.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/uaa/token_coder.rb', line 53

def self.encode(token_body, options = {}, obsolete1 = nil, obsolete2 = nil)
  unless options.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."
    options = {:skey => options }
    options[:pkey], options[:algorithm] = obsolete1, obsolete2
  end
  options = normalize_options(options)
  algo = options[: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), options[:skey], segments.join('.'))
  elsif ["RS256", "RS384", "RS512"].include?(algo)
    sig = options[:pkey].sign(init_digest(algo), segments.join('.'))
  elsif algo == "none"
    sig = ""
  else
    raise ArgumentError, "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.

Parameters:

  • auth_header (String)

    (see Scim.initialize#auth_header)

Returns:

  • (Hash)

    the token contents



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/uaa/token_coder.rb', line 167

def decode(auth_header)
  unless auth_header && (tkn = auth_header.split(' ')).length == 2 && tkn[0] =~ /^bearer$/i
    raise DecodeError, "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 AuthError, "invalid audience: #{auds}"
  end
  exp = reply[:exp] || reply['exp']
  unless exp.is_a?(Integer) && exp > Time.now.to_i
    raise AuthError, "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).

Parameters:

  • algorithm (String) (defaults to: nil)

    – overrides default. See #initialize for possible values.

  • token_body (defaults to: {})

    Contents of the token in any object that can be converted to JSON.

Returns:

  • (String)

    a signed JWT token string in the form “xxxx.xxxxx.xxxx”.



155
156
157
158
159
# File 'lib/uaa/token_coder.rb', line 155

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