Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Attempt to get Twitter request_token using Oauth 1.0 keeps giving '215 Bad Authentication' error

I'm trying to get an Oauth request token from Twitter - I'm following their guide (links below) and I've gone over every single step about 10 times but I cannot figure out why I'm getting this error:

{\"errors\":[{\"code\":32,\"message\":\"Could not authenticate you.\"}]}

They use Oauth 1.0. I'm supposed to combine all the percent-encoded params in my request, percent encode them, add the request method (POST) and the request url, use that plus my api key secret to create a signature, and add the signature to my final Post request. The signature docs are on this page and the subsequent Post request docs are on this page. I used the example code given in the guide to generate a signature and I got the same result the guide did, so I don't think the actual signature generation is a problem.

I triple checked my api key and api key secret and callback url.

Here's my full code. Can anyone see a problem?

require 'net/http'
require 'uri'
require 'json'
require 'base64'
require 'cgi'
require 'openssl'

oauth_callback = "http://www.example.localhost:3000/twittercallback"
oauth_consumer_key = '[KEY]'
oauth_timestamp = Time.now.to_i
oauth_nonce = SecureRandom.hex(10) + oauth_timestamp.to_s
oauth_signature_method = "HMAC-SHA1"
oauth_version = "1.0"


one1 = CGI.escape "oauth_callback"
one2 = CGI.escape oauth_callback
two1 = CGI.escape "oauth_consumer_key"
two2 = CGI.escape oauth_consumer_key
three1 = CGI.escape "oauth_nonce"
three2 = CGI.escape oauth_nonce
four1 = CGI.escape "oauth_signature_method"
four2 = CGI.escape oauth_signature_method
five1 = CGI.escape "oauth_timestamp"
five2 = CGI.escape oauth_timestamp.to_s
six1 = CGI.escape "oauth_version"
six2 = CGI.escape oauth_version
string = "#{one1}=#{one2}&#{two1}=#{two2}&#{three1}=#{three2}&#{four1}=#{four2}&#{five1}=#{five2}&#{six1}=#{six2}"
encoded_string = CGI.escape string
url = "https://api.twitter.com/oauth/request_token"
encoded_url = CGI.escape url
encoded_string = "POST&" + encoded_url + "&" + encoded_string
signing_key = '[SECRET]'
encoded_signing_key = (CGI.escape signing_key) + "&"
digest = OpenSSL::Digest::Digest.new( 'sha1' )
hmac = OpenSSL::HMAC.digest( digest, encoded_signing_key, encoded_string)
signature = Base64.encode64( hmac ).chomp.gsub( /\n/, '' )

uri = URI.parse("https://api.twitter.com/oauth/request_token")
request = Net::HTTP::Post.new(uri)
request.content_type = "application/x-www-form-urlencoded"
oauth = 'OAuth oauth_callback="' + (CGI.escape oauth_callback) + '", oauth_nonce="' + (oauth_nonce + 'dsd') + '", oauth_signature_method="HMAC-SHA1", oauth_timestamp="' + oauth_timestamp.to_s + '", oauth_consumer_key="`<KEY>", oauth_signature="' + (CGI.escape signature) + '", oauth_version="1.0"'
request['Authorization'] = oauth
request.body = ""
req_options = {
    use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
    http.request(request)
end

here's my request["Authorization"] header

"OAuth oauth_callback=\"http%3A%2F%2Fwww.example.localhost%3A3000%2Ftwittercallback\", oauth_nonce=\"e5686f142ca58a45af233fb66876671592457549dsd\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"1592457549\", oauth_consumer_key=\"[KEY]\", oauth_signature=\"n9MNLO%2Ft%2FT6WX0Myu5JcTICXNAQ%3D\", oauth_version=\"1.0\""

and this is the example header from Twitter's docs:

OAuth oauth_nonce="K7ny27JTpKVsTgdyLdDfmQQWVLERj2zAK5BslRsqyw", oauth_callback="http%3A%2F%2Fmyapp.com%3A3005%2Ftwitter%2Fprocess_callback", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1300228849", oauth_consumer_key="OqEqJeafRSF11jBMStrZz", oauth_signature="Pc%2BMLdv028fxCErFyi8KXFM%2BddU%3D", oauth_version="1.0"

like image 448
Joe Morano Avatar asked Jun 15 '20 05:06

Joe Morano


1 Answers

You don't provide the required Authorization header, but separate headers for each parameter. You need to combine the parameters in the following form as a string with OAuth as first:

OAuth oauth_nonce="K7ny27JTpKVsTgdyLdDfmQQWVLERj2zAK5BslRsqyw",oauth_callback="http%3A%2F%2Fmyapp.com%3A3005%2Ftwitter%2Fprocess_callback",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1300228849",oauth_consumer_key="OqEqJeafRSF11jBMStrZz",oauth_signature="Pc%2BMLdv028fxCErFyi8KXFM%2BddU%3D",oauth_version="1.0"

And provide that in the Authorization header:

request['Authorization'] = <OAuth string>

There are a lot of other issues in your code why it's not working:

  • Wrong escaping of the parameters
  • Escaping the signing key
  • Adding dsd to the nonce in the Authorization header
  • Wrong order of data in the Authorization header (needs to be lexicographically, e.g. for most libraries alphabetical)

So this results in the following if we rework your code:

require 'net/http'
require 'uri'
require 'base64'
require 'cgi'
require 'openssl'
require 'securerandom'

oauth_consumer_key = CGI::escape('D6O7BIWc6MTgl0A8UaRRt83In')
oauth_timestamp = CGI::escape(Time.now.to_i.to_s)
oauth_nonce = CGI::escape(SecureRandom.hex(10) + oauth_timestamp.to_s)
oauth_signature_method = CGI::escape("HMAC-SHA1")
oauth_version = CGI::escape("1.0")

one1 = CGI::escape("oauth_consumer_key")
one2 = oauth_consumer_key
two1 = CGI::escape("oauth_nonce")
two2 = oauth_nonce
three1 = CGI::escape("oauth_signature_method")
three2 = oauth_signature_method
four1 = CGI::escape("oauth_timestamp")
four2 = oauth_timestamp
five1 = CGI::escape("oauth_version")
five2 = oauth_version
string = "#{one1}=#{one2}&#{two1}=#{two2}&#{three1}=#{three2}&#{four1}=#{four2}&#{five1}=#{five2}"
encoded_string = CGI.escape string
url = "https://api.twitter.com/oauth/request_token"
encoded_url = CGI.escape url
encoded_string = "POST&" + encoded_url + "&" + encoded_string
signing_key = 'xJPBJ2OdV6nM8r9e6ZysbnHTrZYm4R7LaY9OafNNNY3BkT4Oym'
encoded_signing_key = signing_key + '&'
digest = OpenSSL::Digest.new( 'sha1' )
hmac = OpenSSL::HMAC.digest( digest, encoded_signing_key, encoded_string)
signature = Base64.encode64( hmac ).chomp.gsub( /\n/, '' )

uri = URI.parse("https://api.twitter.com/oauth/request_token")
request = Net::HTTP::Post.new(uri)
request.content_type = "application/x-www-form-urlencoded"
oauth = 'OAuth ' + one1 + '="' + oauth_consumer_key + '", ' + two1 + '="' + oauth_nonce + '", ' + CGI::escape('oauth_signature') + '="' + (CGI.escape signature) + '", ' + three1 + '="HMAC-SHA1", ' + four1 + '="' + oauth_timestamp.to_s  + '", ' + five1 + '="1.0"'
puts oauth
request['Authorization'] = oauth
request.body = ""
req_options = {
    use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
    http.request(request)
end
puts response
puts response.body

I would also like to give you a way to handle the parameters in a better way to generate the base_string and the header:

require 'net/http'
require 'uri'
require 'base64'
require 'cgi'
require 'openssl'
require 'securerandom'

oauth_consumer_key = '<KEY>'
oauth_consumer_secret = '<SECRET>'
oauth_consumer_secret += '&'
oauth_timestamp = Time.now.getutc.to_i.to_s
oauth_nonce = SecureRandom.hex(10) + oauth_timestamp
oauth_signature_method = "HMAC-SHA1"
oauth_version = "1.0"

url = "https://api.twitter.com/oauth/request_token"
uri = URI.parse(url)
params = {
  'oauth_consumer_key' => oauth_consumer_key,
  'oauth_nonce' => oauth_nonce,
  'oauth_signature_method' => oauth_signature_method,
  'oauth_timestamp' => oauth_timestamp.to_s,
  'oauth_version' => oauth_version
}

base_string = "POST&" + CGI::escape(url) + "&" + CGI::escape(params.sort.collect{ |k,v| "#{CGI::escape(k)}=#{CGI::escape(v)}" }.join('&'))
oauth_signature = Base64.encode64("#{OpenSSL::HMAC.digest('sha1', oauth_consumer_secret, base_string)}").chomp

params['oauth_signature'] = oauth_signature

uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

header = "OAuth " + params.sort.collect {|k,v| "#{CGI::escape(k)}=\"#{CGI::escape(v)}\""}.join(", ")
rsp, data = http.post(uri, nil, {'Authorization' => header })

puts rsp
puts rsp.body

Another suggestion is to use the existing OAuth gem available. When you use this well known library you are also able to get support from Twitter.

gem install oauth
require 'oauth'

oauth_consumer = OAuth::Consumer.new("<KEY>", "<SECRET>", :site => "https://api.twitter.com")

access_token = OAuth::AccessToken.new(oauth_consumer)

request_token = access_token.request(:post, "/oauth/request_token")
rsp = request_token.body
puts rsp
like image 166
rfkortekaas Avatar answered Sep 30 '22 18:09

rfkortekaas