module Sandal
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.
Constants
- 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.
- VERSION
The semantic version of the library.
Public Class Methods
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.
@param token [String] The encoded JSON Web Token. @param depth [Integer] The maximum depth of token nesting to decode to. @yieldparam header [Hash] The JWT header values. @yieldparam options [Hash] (Optional) A hash that can be used to override the default options. @yieldreturn [#valid? or decrypt] The signature validator if the token is signed, or the token decrypter if the
token is encrypted.
@return [Hash or String] The payload of the token as a Hash if it was JSON, otherwise as a String. @raise [Sandal::TokenError] The token is invalid or not supported.
# 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
Overrides the default options.
@param defaults [Hash] The options to override (see {DEFAULT_OPTIONS} for details). @return [Hash] The new default options.
# File lib/sandal.rb, line 75 def self.default!(defaults) DEFAULT_OPTIONS.merge!(defaults) end
Creates a signed JSON Web Token.
@param payload [String or Hash] The payload of the token. Hashes will be encoded as JSON. @param signer [#name,#sign] The token signer, which may be nil for an unsigned token. @param header_fields [Hash] Header fields for the token (note: do not include “alg”). @return [String] A signed JSON Web Token.
# 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
Creates an encrypted JSON Web Token.
@param payload [String] The payload of the token. @param encrypter [#name,#alg,#encrypt] The token encrypter. @param header_fields [Hash] Header fields for the token (note: do not include “alg” or “enc”). @return [String] An encrypted JSON Web Token.
# 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
Checks whether a token is encrypted.
@param token [String or Array] The token, or token parts. @return [Boolean] true if the token is encrypted; otherwise false.
# 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
Checks whether a token is signed.
@param token [String or Array] The token, or token parts. @return [Boolean] true if the token is signed; otherwise false.
# 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
Private Class Methods
Decodes the parts of a token.
# File lib/sandal.rb, line 215 def self.decode_token_parts(parts) parts = parts.map { |part| Sandal::Util.jwt_base64_decode(part) } parts[0] = Sandal::Json.load(parts[0]) parts rescue raise InvalidTokenError, "Invalid token encoding." end
Parses the content of a token and validates the claims if is JSON claims.
# File lib/sandal.rb, line 224 def self.parse_and_validate(payload, options) claims = Sandal::Json.load(payload) rescue nil if claims claims.extend(Sandal::Claims).validate_claims(options) else payload end end
Decodes and validates a signed JSON Web Token.
# File lib/sandal.rb, line 206 def self.validate_signature(parts, signature, validator) raise UnsupportedTokenError, "Unsupported signature method." if validator.nil? secured_input = parts.take(2).join(".") unless validator.valid?(signature, secured_input) raise InvalidTokenError, "Invalid signature." end end