Module: BetterAuth::Crypto::JWE

Defined in:
lib/better_auth/crypto/jwe.rb

Constant Summary collapse

ALG =
"dir"
ENC =
"A256CBC-HS512"
INFO =
"BetterAuth.js Generated Encryption Key"
CLOCK_TOLERANCE =
15

Class Method Summary collapse

Class Method Details

.decode(token, secret, salt) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/better_auth/crypto/jwe.rb', line 33

def decode(token, secret, salt)
  return nil if token.to_s.empty?

  header = protected_header(token)
  return nil unless valid_header?(header)
  return nil unless header["kid"].nil? || header["kid"] == thumbprint(encryption_key(secret, salt))

  payload = JSON.parse(::JWE.decrypt(token.to_s, encryption_key(secret, salt)))
  return nil if expired?(payload)

  payload
rescue JSON::ParserError, ArgumentError, ::JWE::DecodeError, ::JWE::InvalidData, ::JWE::BadCEK
  nil
end

.encode(payload, secret, salt, expires_in: 3600) ⇒ Object



23
24
25
26
27
28
29
30
31
# File 'lib/better_auth/crypto/jwe.rb', line 23

def encode(payload, secret, salt, expires_in: 3600)
  claims = Crypto.stringify_keys(payload).merge(
    "iat" => Time.now.to_i,
    "exp" => Time.now.to_i + expires_in.to_i,
    "jti" => SecureRandom.uuid
  )
  key = encryption_key(secret, salt)
  ::JWE.encrypt(JSON.generate(claims), key, alg: ALG, enc: ENC, kid: thumbprint(key))
end

.encryption_key(secret, salt) ⇒ Object



48
49
50
# File 'lib/better_auth/crypto/jwe.rb', line 48

def encryption_key(secret, salt)
  OpenSSL::KDF.hkdf(secret.to_s, salt: salt.to_s, info: INFO, length: 64, hash: "SHA256")
end

.expired?(payload) ⇒ Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/better_auth/crypto/jwe.rb', line 71

def expired?(payload)
  payload["exp"] && payload["exp"].to_i < Time.now.to_i - CLOCK_TOLERANCE
end

.protected_header(token) ⇒ Object



60
61
62
63
64
65
# File 'lib/better_auth/crypto/jwe.rb', line 60

def protected_header(token)
  first_segment = token.to_s.split(".", 2).first
  JSON.parse(Crypto.base64url_decode(first_segment))
rescue JSON::ParserError, ArgumentError
  {}
end

.thumbprint(key) ⇒ Object



52
53
54
55
56
57
58
# File 'lib/better_auth/crypto/jwe.rb', line 52

def thumbprint(key)
  jwk = {
    "k" => Base64.urlsafe_encode64(key, padding: false),
    "kty" => "oct"
  }
  Crypto.base64url_encode(OpenSSL::Digest.digest("SHA256", JSON.generate(jwk)))
end

.valid_header?(header) ⇒ Boolean

Returns:

  • (Boolean)


67
68
69
# File 'lib/better_auth/crypto/jwe.rb', line 67

def valid_header?(header)
  header["alg"] == ALG && header["enc"] == ENC
end