When we decide to use Oauth 1 for authentication, we need to answer the first question: ‘How is the signature generated?’. It took me 3 days to work it out before I could get data from our client’s NETSUITE account.
I want to share with you my first raw Ruby code and hope it will save you a bit of time.
# NETSUITE Account vars
client_key = 'xxx'
client_secret = 'xxx'
token_id = 'xxx'
token_secret = 'xxx'
account_id = 'xxx'
# OAuth vars
oauth_signature_method = 'HMAC-SHA256'
oauth_timestamp = Time.now.to_i
oauth_nonce = ERB::Util.url_encode(SecureRandom.base64)
http_request_method = 'POST' #'GET'
version = '1.0'
# Init url
url_text = "https://#{account_id.downcase}.suitetalk.api.netsuite.com/services/rest/query/v1/suiteql"
# URL params
params = { limit: 5 }
params = {}
# We need to percent encode URL params
params_text = params.map {|k, v| "#{k}=#{ERB::Util.url_encode(v.to_s)}" }.join("\&")
url = URI(url_text)
url.query = params_text
# Init request parameters for signature base string
# The key name must be in alphabet order
request_params = {
oauth_consumer_key: client_key,
oauth_nonce: oauth_nonce,
oauth_signature_method: oauth_signature_method,
oauth_timestamp: oauth_timestamp,
oauth_token: token_id,
oauth_version: version
}
request_params_ordered = request_params.dup
# We need to put all URL params to the request parameters of the signature base string
params.each do |k, v|
request_params_ordered[k] = ERB::Util.url_encode(v.to_s) # Percent encode
end
request_params_ordered = request_params_ordered.sort_by { |k, _| k }
normalized_request_parameters = request_params_ordered.map {|k, v| "#{k}=#{v.to_s}" }.join("\&")
# Init base string for signature
base_string = "#{http_request_method}\&#{ERB::Util.url_encode(url_text)}\&#{ERB::Util.url_encode(normalized_request_parameters)}"
# Init secret key for signature
secret_key = "#{client_secret}\&#{token_secret}"
# Generate signature
hmac_signature = ERB::Util.url_encode(Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', secret_key, base_string)))
# Init Authorization header
authorization = [
"realm=\"#{account_id.upcase}\"",
"oauth_consumer_key=\"#{client_key}\"",
"oauth_token=\"#{token_id}\"",
"oauth_signature_method=\"#{oauth_signature_method}\"",
"oauth_timestamp=\"#{oauth_timestamp}\"",
"oauth_nonce=\"#{oauth_nonce}\"",
"oauth_version=\"#{version}\"",
"oauth_signature=\"#{hmac_signature}\""
].join(',')
request_body = {
q: "SELECT * FROM account"
}
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
method_class_name = "Net::HTTP::#{http_request_method.titlecase}".constantize
request = method_class_name.new(url)
# request = Net::HTTP::Post.new(url)
# request = Net::HTTP::Get.new(url)
request["Authorization"] = "OAuth #{authorization}"
request["prefer"] = "transient"
request["Content-Type"] = "application/json"
request.body = JSON.dump(request_body)
response = https.request(request)
puts response.read_body
Happy coding!
References: https://developer.twitter.com/en/docs/authentication/oauth-1-0a/creating-a-signature