I wrestled with step 1 on this page (the request_token part)âŚ
https://dev.twitter.com/docs/auth/implementing-sign-twitter
âŚfor essentially 2 full days and just got it working. I was told to use twitter4j and and some of the other ones but thatâs problematic for a few reasons; efficiency, security and stability are unknown unless you dig through all the source code and test it yourself. If youâre going to do that, you might as well build it yourself, right?
Anyway, since good examples were hard (er, impossible) to come by, I figured Iâd share the wealth of what I learned. First, a couple of clarifying notes that I tripped up on that might help people:
Putting together the request string sucks, but how to do it is well documented here: https://dev.twitter.com/docs/auth/authorizing-request . The request_token flow differs in a few important â and tricky â ways that werenât entirely obvious at the beginning:
a. Use https://api.twitter.com/oauth/request_token instead of the URL endpoint provided â ok, maybe this one IS obvious
b. request_token has no post body. In other words, ignore the âstatus=Hello%20LadiesâŚâ part underneath the HTTP headers
c. Do not include any parameters to the request target. That is, the âinclude_entitiesâ part is unnecessary for request_token. It should just be âPOST /oauth/request_token HTTP/1.1â on the top line
d. The signature base string has to be assembled very precisely before Sha-1 hashing it. If youâre not using a callback, leave the âoauth_callback=â part out entirely. The params need to be alphabetized before hashing. The timestamp has to be within 5 minutes of twitter time. And everything has to be escaped properly. Follow the directions (linked above) very very closely.
e. When youâve finally assembled the signature base string correctly, you have to hash it. Normally this would be done with âconsumer_secret&user_access_tokenâ for normal api requests, but we donât know the user access token yet. So the value to hash against is just âconsumer_secret&â. (The & is not escaped in this case as it is just being used to hash against, not for any wire communication.)
f. Use the example data from my first link until you have it right. If you try to execute the requests, they will fail because the key is invalid and because the timestamp is long overdue, but itâs useful to use the example data to match against the oauth_signature it produces: âF1Li3tvehgcraF8DMJ7OyxO4w9Y%3Dâ Until youâre able to produce that oauth_signature value using the sample data, youâve still got something wrong in your signature process.
Without further ado, here is my working request_token flow:
/* The following works with example credentials from: https://dev.twitter.com/docs/auth/implementing-sign-twitter
String oauth_consumer_key = "cChZNFj6T5R0TigYB9yd1w"; // fake from example
String oauth_nonce = "ea9ec8429b68d6b77cd5600adbbb0456"; // fake from example
String oauth_signature_method = "HMAC-SHA1";
String oauth_timestamp = "1318467427"; // fake from example
String oauth_callback_value = "http%3A%2F%2Flocalhost%2Fsign-in-with-twitter%2F";
// the parameter string must be in alphabetical order
String parameter_string = "oauth_callback=" + oauth_callback_value + "&oauth_consumer_key=" + oauth_consumer_key + "&oauth_nonce=" +
oauth_nonce + "&oauth_signature_method=" + oauth_signature_method + "&oauth_timestamp=" + oauth_timestamp + "&oauth_version=1.0";
System.out.println("parameter_string=" + parameter_string);
String signature_base_string = "POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&" + URLEncoder.encode(parameter_string, "UTF-8");
System.out.println("signature_base_string=" + signature_base_string);
String oauth_signature = "";
try {
oauth_signature = computeSignature(signature_base_string, "L8qq9PZyRg6ieKGEKhZolGC0vJWLw8iEJ88DRdyOg&"); // fake from example, note the & at the end. Normally the user access_token would go here, but we don't know it yet for request_token flow
System.out.println("oauth_signature=" + URLEncoder.encode(oauth_signature, "UTF-8")); // this should match F1Li3tvehgcraF8DMJ7OyxO4w9Y%3D
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String authorization_header_string = "OAuth oauth_callback=\"" + oauth_callback_value + "\",oauth_consumer_key=\"" + oauth_consumer_key + "\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"" +
oauth_timestamp + "\",oauth_nonce=\"" + oauth_nonce + "\",oauth_version=\"1.0\",oauth_signature=\"" + URLEncoder.encode(oauth_signature, "UTF-8") + "\"";
System.out.println("authorization_header_string=" + authorization_header_string);
*/
// This is the working flow
String oauth_signature_method = "HMAC-SHA1";
String oauth_consumer_key = "KEYKEYKEYKEYKEY";
String uuid_string = UUID.randomUUID().toString();
uuid_string = uuid_string.replaceAll("-", "");
String oauth_nonce = uuid_string; // any relatively random alphanumeric string will work here. I used UUID minus "-" signs
String oauth_timestamp = (new Long(timestamp_at_entry/1000)).toString(); // get current time in milliseconds, then divide by 1000 to get seconds
// I'm not using a callback value. Otherwise, you'd need to include it in the parameter string like the example above
// the parameter string must be in alphabetical order
String parameter_string = "oauth_consumer_key=" + oauth_consumer_key + "&oauth_nonce=" + oauth_nonce + "&oauth_signature_method=" + oauth_signature_method + "&oauth_timestamp=" + oauth_timestamp + "&oauth_version=1.0";
System.out.println("parameter_string=" + parameter_string);
String signature_base_string = "POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&" + URLEncoder.encode(parameter_string, "UTF-8");
System.out.println("signature_base_string=" + signature_base_string);
String oauth_signature = "";
try {
oauth_signature = computeSignature(signature_base_string, "3yasdfasmyconsumersecretfasd53&"); // note the & at the end. Normally the user access_token would go here, but we don't know it yet for request_token
System.out.println("oauth_signature=" + URLEncoder.encode(oauth_signature, "UTF-8"));
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String authorization_header_string = "OAuth oauth_consumer_key=\"" + oauth_consumer_key + "\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"" +
oauth_timestamp + "\",oauth_nonce=\"" + oauth_nonce + "\",oauth_version=\"1.0\",oauth_signature=\"" + URLEncoder.encode(oauth_signature, "UTF-8") + "\"";
System.out.println("authorization_header_string=" + authorization_header_string);
String oauth_token = "";
HttpClient httpclient = new DefaultHttpClient();
try {
HttpPost httppost = new HttpPost("https://api.twitter.com/oauth/request_token");
httppost.setHeader("Authorization",authorization_header_string);
ResponseHandler<String> responseHandler = new BasicResponseHandler();
String responseBody = httpclient.execute(httppost, responseHandler);
oauth_token = responseBody.substring(responseBody.indexOf("oauth_token=") + 12, responseBody.indexOf("&oauth_token_secret="));
System.out.println(responseBody);
}
catch(ClientProtocolException cpe) { System.out.println(cpe.getMessage()); }
catch(IOException ioe) { System.out.println(ioe.getMessage()); }
finally { httpclient.getConnectionManager().shutdown(); }
To compute signatures, use this function I found on stack overflow. It depends on apache-commons-codec:
private static String computeSignature(String baseString, String keyString) throws GeneralSecurityException, UnsupportedEncodingException {
SecretKey secretKey = null;
byte[] keyBytes = keyString.getBytes();
secretKey = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKey);
byte[] text = baseString.getBytes();
return new String(Base64.encodeBase64(mac.doFinal(text))).trim();
}
After youâve got the redirect url, the user will be directed to twitter to grant access. In my example, they are shown a pin which then has to be plugged in elsewhere. But at least Iâve got Step 1 nailed down!