Module: AnyCable::JWT::BasicImpl

Defined in:
lib/anycable/jwt.rb

Overview

Basic JWT encode/decode implementation suitable to our needs and not requiring external dependencies

Constant Summary collapse

ALGORITHM =
"HS256"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.decode(token, secret_key) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/anycable/jwt.rb', line 31

def decode(token, secret_key)
  header, payload, signature = token.split(".")
  # Check segments
  raise DecodeError, "Not enough or too many segments" unless header && payload && signature

  # Verify the algorithm
  decoded_header = ::JSON.parse(::Base64.urlsafe_decode64(header))
  raise DecodeError, "Algorithm not supported" unless decoded_header["alg"] == ALGORITHM

  # Verify the signature
  expected_signature = sign("#{header}.#{payload}", secret_key)
  raise VerificationError, "Signature verification failed" unless secure_compare(signature, expected_signature)

  # Verify expiration
  decoded_payload = ::JSON.parse(::Base64.urlsafe_decode64(payload))
  if decoded_payload.key?("exp")
    raise ExpiredSignature, "Signature has expired" if Time.now.to_i >= decoded_payload["exp"]
  end

  decoded_payload
rescue JSON::ParserError, ArgumentError
  raise DecodeError, "Invalid segment encoding"
end

.encode(payload, secret_key) ⇒ Object



21
22
23
24
25
26
27
28
29
# File 'lib/anycable/jwt.rb', line 21

def encode(payload, secret_key)
  payload = ::Base64.urlsafe_encode64(payload.to_json, padding: false)
  headers = ::Base64.urlsafe_encode64({"alg" => ALGORITHM}.to_json, padding: false)

  header = "#{headers}.#{payload}"
  signature = sign(header, secret_key)

  "#{header}.#{signature}"
end

.sign(data, secret_key) ⇒ Object



72
73
74
75
76
77
# File 'lib/anycable/jwt.rb', line 72

def sign(data, secret_key)
  ::Base64.urlsafe_encode64(
    ::OpenSSL::HMAC.digest("SHA256", secret_key, data),
    padding: false
  )
end

Instance Method Details

#secure_compare(a, b) ⇒ Object



59
60
61
62
63
# File 'lib/anycable/jwt.rb', line 59

def secure_compare(a, b)
  return false if a.bytesize != b.bytesize

  OpenSSL.fixed_length_secure_compare(a, b)
end