Module: GRApiManager::JWT
- Defined in:
- lib/gr_api_manager.rb
Overview
JWT — Zero-dependency JSON Web Token encoder and decoder (HS256).
Defined Under Namespace
Classes: DecodeError, ExpiredSignature
Class Method Summary collapse
- .base64url_decode(str) ⇒ Object
- .base64url_encode(str) ⇒ Object
-
.decode(token, secret) ⇒ Object
Decodes and verifies a JWT token.
-
.encode(payload, secret, exp: nil, algorithm: 'HS256') ⇒ Object
Encodes a payload hash into a JWT token signed with HMAC-SHA256.
Class Method Details
.base64url_decode(str) ⇒ Object
49 50 51 52 53 54 |
# File 'lib/gr_api_manager.rb', line 49 def self.base64url_decode(str) padded = str + ('=' * ((4 - (str.length % 4)) % 4)) Base64.urlsafe_decode64(padded) rescue ArgumentError => e raise DecodeError, "Invalid Base64URL string: #{e.}" end |
.base64url_encode(str) ⇒ Object
45 46 47 |
# File 'lib/gr_api_manager.rb', line 45 def self.base64url_encode(str) Base64.urlsafe_encode64(str, padding: false) end |
.decode(token, secret) ⇒ Object
Decodes and verifies a JWT token. Returns the payload hash with symbolized keys.
79 80 81 82 83 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 |
# File 'lib/gr_api_manager.rb', line 79 def self.decode(token, secret) raise ArgumentError, "JWT secret cannot be blank" if secret.to_s.strip.empty? raise DecodeError, "Token cannot be blank" if token.nil? || token.to_s.strip.empty? parts = token.to_s.split('.') raise DecodeError, "Invalid JWT format. Expected 3 segments separated by dots." unless parts.size == 3 header_b64, payload_b64, signature_b64 = parts signing_input = "#{header_b64}.#{payload_b64}" digest = OpenSSL::Digest.new('sha256') expected_sig = OpenSSL::HMAC.digest(digest, secret.to_s, signing_input) actual_sig = base64url_decode(signature_b64) is_valid = if OpenSSL.respond_to?(:secure_compare) OpenSSL.secure_compare(expected_sig, actual_sig) else expected_sig == actual_sig end raise DecodeError, "Invalid JWT signature" unless is_valid payload_json = base64url_decode(payload_b64) payload = JSON.parse(payload_json, symbolize_names: true) if payload[:exp] exp_time = payload[:exp].to_i raise ExpiredSignature, "JWT signature has expired" if Time.now.to_i > exp_time end payload rescue JSON::ParserError => e raise DecodeError, "Invalid JSON payload in token: #{e.}" end |
.encode(payload, secret, exp: nil, algorithm: 'HS256') ⇒ Object
Encodes a payload hash into a JWT token signed with HMAC-SHA256. Options:
exp: Integer – expiration timestamp (epoch in seconds).
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/gr_api_manager.rb', line 59 def self.encode(payload, secret, exp: nil, algorithm: 'HS256') raise ArgumentError, "JWT secret cannot be blank" if secret.to_s.strip.empty? data = payload.dup data = data.transform_keys(&:to_sym) if data.is_a?(Hash) data[:exp] = exp.to_i if exp header = { typ: 'JWT', alg: algorithm } header_b64 = base64url_encode(header.to_json) payload_b64 = base64url_encode(data.to_json) signing_input = "#{header_b64}.#{payload_b64}" digest = OpenSSL::Digest.new('sha256') signature = OpenSSL::HMAC.digest(digest, secret.to_s, signing_input) signature_b64 = base64url_encode(signature) "#{signing_input}.#{signature_b64}" end |