Module: Sandal

Defined in:
lib/sandal.rb,
lib/sandal/enc.rb,
lib/sandal/sig.rb,
lib/sandal/json.rb,
lib/sandal/util.rb,
lib/sandal/claims.rb,
lib/sandal/sig/es.rb,
lib/sandal/sig/hs.rb,
lib/sandal/sig/rs.rb,
lib/sandal/enc/alg.rb,
lib/sandal/version.rb,
lib/sandal/enc/agcm.rb,
lib/sandal/enc/acbc_hs.rb,
lib/sandal/enc/alg/rsa.rb,
lib/sandal/enc/alg/direct.rb

Overview

A library for creating and reading JSON Web Tokens (JWT), supporting JSON Web Signatures (JWS) and JSON Web Encryption (JWE).

Currently supports draft-07 of the JWT spec, and draft-10 of the JWS and JWE specs.

Defined Under Namespace

Modules: Claims, Enc, Json, Sig Classes: ClaimError, Error, ExpiredTokenError, InvalidTokenError, KeyError, TokenError, UnsupportedTokenError

Constant Summary collapse

DEFAULT_OPTIONS =

The default options for token handling.

ignore_exp

Whether to ignore the expiry date of the token. This setting is just to help get things working and should always be false in real apps!

ignore_nbf

Whether to ignore the not-before date of the token. This setting is just to help get things working and should always be false in real apps!

ignore_signature

Whether to ignore the signature of signed (JWS) tokens. This setting is just tohelp get things working and should always be false in real apps!

max_clock_skew

The maximum clock skew, in seconds, when validating times. If your server time is out of sync with the token server then this can be increased to take that into account. It probably shouldn’t be more than about 300.

signature_policy

The policy for requiring signatures in tokens. The possible values are:

  • :strict (default) - The innermost token must be signed. This is the recommended policy.

  • :none - No signature is required. This really isn’t recommended.

valid_iss

A list of valid token issuers, if validation of the issuer claim is required.

valid_aud

A list of valid audiences, if validation of the audience claim is required.

{
  ignore_exp: false,
  ignore_nbf: false,
  ignore_signature: false,
  max_clock_skew: 0,
  signature_policy: :strict,
  valid_iss: [],
  valid_aud: []
}
VERSION =

The semantic version of the library.

"0.6.0"

Class Method Summary collapse

Class Method Details

.decode_token(token, depth = 16) {|header, options| ... } ⇒ Hash or String

Decodes and validates a signed and/or encrypted JSON Web Token, recursing into any nested tokens, and returns the payload.

The block is called with the token header as the first parameter, and should return the appropriate signature or decryption method to either validate the signature or decrypt the token as applicable. When the tokens are nested, this block will be called once per token. It can optionally have a second options parameter which can be used to override the DEFAULT_OPTIONS on a per-token basis; options are not persisted between yields.

Parameters:

  • token (String)

    The encoded JSON Web Token.

  • depth (Integer) (defaults to: 16)

    The maximum depth of token nesting to decode to.

Yield Parameters:

  • header (Hash)

    The JWT header values.

  • options (Hash)

    (Optional) A hash that can be used to override the default options.

Yield Returns:

  • (#valid? or #decrypt)

    The signature validator if the token is signed, or the token decrypter if the token is encrypted.

Returns:

  • (Hash or String)

    The payload of the token as a Hash if it was JSON, otherwise as a String.

Raises:



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/sandal.rb', line 162

def self.decode_token(token, depth = 16)
  parts = token.split(".")
  decoded_parts = decode_token_parts(parts)
  header = decoded_parts[0]

  options = DEFAULT_OPTIONS.clone
  decoder = yield header, options if block_given?

  if is_encrypted?(parts)
    payload = decoder.decrypt(parts)
    if header.has_key?("zip")
      unless header["zip"] == "DEF"
        raise Sandal::InvalidTokenError, "Invalid zip algorithm."
      end
      payload = Zlib::Inflate.inflate(payload)
    end
  else
    payload = decoded_parts[1]
    unless options[:ignore_signature]
      validate_signature(parts, decoded_parts[2], decoder) 
    end
  end

  if header.has_key?("cty") && header["cty"] =~ /\AJWT\Z/i
    if depth > 0
      if block_given?
        decode_token(payload, depth - 1, &Proc.new)
      else 
        decode_token(payload, depth - 1)
      end
    else
      payload
    end
  else
    if options[:signature_policy] == :strict && !is_signed?(parts)
      raise Sandal::UnsupportedTokenError, "The innermost token is not signed."
    end
    parse_and_validate(payload, options)
  end
end

.default!(defaults) ⇒ Hash

Overrides the default options.

Parameters:

  • defaults (Hash)

    The options to override (see DEFAULT_OPTIONS for details).

Returns:

  • (Hash)

    The new default options.



75
76
77
# File 'lib/sandal.rb', line 75

def self.default!(defaults)
  DEFAULT_OPTIONS.merge!(defaults)
end

.encode_token(payload, signer, header_fields = nil) ⇒ String

Creates a signed JSON Web Token.

Parameters:

  • payload (String or Hash)

    The payload of the token. Hashes will be encoded as JSON.

  • signer (#name, #sign)

    The token signer, which may be nil for an unsigned token.

  • header_fields (Hash) (defaults to: nil)

    Header fields for the token (note: do not include “alg”).

Returns:

  • (String)

    A signed JSON Web Token.



109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/sandal.rb', line 109

def self.encode_token(payload, signer, header_fields = nil)
  signer ||= Sandal::Sig::NONE

  header = {}
  header["alg"] = signer.name
  header = header_fields.merge(header) if header_fields
  header = Sandal::Json.dump(header)

  payload = Sandal::Json.dump(payload) unless payload.is_a?(String)

  sec_input = [header, payload].map { |p| Sandal::Util.jwt_base64_encode(p) }.join(".")
  signature = signer.sign(sec_input)
  [sec_input, Sandal::Util.jwt_base64_encode(signature)].join(".")
end

.encrypt_token(payload, encrypter, header_fields = nil) ⇒ String

Creates an encrypted JSON Web Token.

Parameters:

  • payload (String)

    The payload of the token.

  • encrypter (#name, #alg, #encrypt)

    The token encrypter.

  • header_fields (Hash) (defaults to: nil)

    Header fields for the token (note: do not include “alg” or “enc”).

Returns:

  • (String)

    An encrypted JSON Web Token.



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/sandal.rb', line 130

def self.encrypt_token(payload, encrypter, header_fields = nil)
  header = {}
  header["enc"] = encrypter.name
  header["alg"] = encrypter.alg.name
  header = header_fields.merge(header) if header_fields

  if header.has_key?("zip")
    unless header["zip"] == "DEF"
      raise ArgumentError, "Invalid zip algorithm."
    end
    payload = Zlib::Deflate.deflate(payload, Zlib::BEST_COMPRESSION)
  end 

  encrypter.encrypt(Sandal::Json.dump(header), payload)
end

.is_encrypted?(token) ⇒ Boolean

Checks whether a token is encrypted.

Parameters:

  • token (String or Array)

    The token, or token parts.

Returns:

  • (Boolean)

    true if the token is encrypted; otherwise false.



83
84
85
86
87
88
89
# File 'lib/sandal.rb', line 83

def self.is_encrypted?(token)
  if token.is_a?(String)
    token.count(".") == 4
  else
    token.count == 5
  end
end

.is_signed?(token) ⇒ Boolean

Checks whether a token is signed.

Parameters:

  • token (String or Array)

    The token, or token parts.

Returns:

  • (Boolean)

    true if the token is signed; otherwise false.



95
96
97
98
99
100
101
# File 'lib/sandal.rb', line 95

def self.is_signed?(token)
  if token.is_a?(String)
    !token.end_with?(".") && token.count(".") == 2
  else
    token.count == 3 && !token[2].nil? && !token[2].empty?
  end
end