Hello I hope you are well. Recently, I have a problem using the Twitter API precisely at the time of publishing a tweet. According to the documentation here, my request must contain in its header a field oauth_timestamp which is the number of seconds since the epoch(seconds since the Unix epoch).
With Dart(Flutter) I retrieve the number of milliseconds elapsed since the epoch which I then divide by 1000 to obtain this value in seconds. Ex:
final oAuthTimestamp =
Uri.encodeComponent('${DateTime.now().millisecondsSinceEpoch / 1000}');
The values must be encoded in percentage encoding, that's why I put the whole date in the Uri.encodeComponent() method.
But what's weird is that the Twitter API sends me an error telling me that my timestamp is out of bounds, which I can't understand why I get this error.
{"errors":[{"code":135,"message":"Timestamp out of bounds."}]}
Can you help me resolve this error? And tell me the mistakes I made.
Thank you.
Here is below my method to publish a tweet using the Twitter API
Future<void> tweet() async {
final httpMethod = 'POST';
final baseURL = 'https://api.twitter.com/1.1/statuses/update.json';
final status =
Uri.encodeComponent('Hello this is my first Tweet using Twitter API');
final includeEntities = Uri.encodeComponent('true');
final oAuthConsumerKey = Uri.encodeComponent('my_consumer_key'); //hidded for security purpose
final consumerSecretKey = Uri.encodeComponent('my_consumer_secret_key'); //hidded for security purpose
final oAuthNonce = Uri.encodeComponent(generateRandomString()); //METHOD TO GENERATE RANDOM STRING ATTACHED BELOW
final oAuthSignatureMethod = Uri.encodeComponent('HMAC-SHA1');
final oAuthTimestamp =
Uri.encodeComponent('${DateTime.now().millisecondsSinceEpoch / 1000}');
final accessToken = await _twitterAuthService.accessToken; //GETTING MY CURRENT USER ACCESS TOKEN
final secretToken = await _twitterAuthService.secretToken; //GETTING MY CURRENT USER SECRET TOKEN
final oAuthSecretToken = Uri.encodeComponent(secretToken);
final oAuthToken = Uri.encodeComponent(accessToken);
final oAuthVersion = Uri.encodeComponent('1.0');
//THIS MY PERCENTAGE ENCODED FIELDS KEY NAME
final includeEntitiesField = Uri.encodeComponent('include_entities');
final oAuthConsumerKeyField = Uri.encodeComponent('oauth_consumer_key');
final oAuthNonceField = Uri.encodeComponent('oauth_nonce');
final oAuthSignatureField = Uri.encodeComponent('oauth_signature');
final oAuthSignatureMethodField =
Uri.encodeComponent('oauth_signature_method');
final oAuthTimestampField = Uri.encodeComponent('oauth_timestamp');
final oAuthTokenField = Uri.encodeComponent('oauth_token');
final oAuthVersionField = Uri.encodeComponent('oauth_version');
final statusField = Uri.encodeComponent('status');
final parameterString = Uri.encodeComponent(
'$includeEntitiesField=$includeEntities&$oAuthConsumerKeyField=$oAuthConsumerKey&$oAuthNonceField=$oAuthNonce&$oAuthSignatureMethodField=$oAuthSignatureMethod&$oAuthTimestampField=$oAuthTimestamp&$oAuthTokenField=$oAuthToken&$oAuthVersionField=$oAuthVersion&$statusField=$status');
final encodedBaseUrl = Uri.encodeComponent(baseURL);
final signatureBaseString = '$httpMethod&$encodedBaseUrl&$parameterString';
final signingKey = '$consumerSecretKey&$oAuthSecretToken';
final hmacSCHA1 = Hmac(sha1, convert.utf8.encode(signingKey))
.convert(convert.utf8.encode(signatureBaseString));
final oAuthSignature =
Uri.encodeComponent(convert.base64.encode(hmacSCHA1.bytes));
final finalUrl = '$baseURL?$statusField=$status';
final headerString =
'OAuth $oAuthConsumerKeyField="$oAuthConsumerKey", $oAuthNonceField="$oAuthNonce", $oAuthSignatureField=$oAuthSignature, $oAuthSignatureMethodField=$oAuthSignatureMethod, $oAuthTimestampField=$oAuthTimestamp, $oAuthTokenField=$oAuthToken, $oAuthVersionField=$oAuthVersion';
final response = await _client.post(finalUrl,
headers: {Fields.authorization.toLowerCase(): headerString});
print('REQUEST RESPONSE. \nbody = ${response.body}');
}
here is the method I use to generate an oauth_nonce:
String generateRandomString() {
final random = Random();
final chars =
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
final generatedString = String.fromCharCodes(
Iterable.generate(
42,
(_) {
final generatedChars = chars.codeUnitAt(random.nextInt(chars.length));
return generatedChars;
},
),
);
return generatedString;
}
useful link: Twitter API documentation to POST a Tweet and Create a oauth_signature
P.S: My unsing cypto for hmac-sha1 encryption
I solved the problems that prevented me from publishing a tweet using the Twitter API.
First of all: I kept my way of retrieving elapsed time (in seconds) since epoch
final oAuthTimestamp = Uri.encodeComponent('${DateTime.now().millisecondsSinceEpoch / 1000}');
And finally secondly:
I was getting an error from the Twitter API telling me {"errors":[{"code":32,"message":"Could not authenticate you."}]}
I simply removed the include_entities field when creating a parameterString
Before
final parameterString = Uri.encodeComponent(
'$includeEntitiesField=$includeEntities&$oAuthConsumerKeyField=$oAuthConsumerKey&$oAuthNonceField=$oAuthNonce&$oAuthSignatureMethodField=$oAuthSignatureMethod&$oAuthTimestampField=$oAuthTimestamp&$oAuthTokenField=$oAuthToken&$oAuthVersionField=$oAuthVersion&$statusField=$status');
After
final parameterString = Uri.encodeComponent(
'$oAuthConsumerKeyField=$oAuthConsumerKey&$oAuthNonceField=$oAuthNonce&$oAuthSignatureMethodField=$oAuthSignatureMethod&$oAuthTimestampField=$oAuthTimestamp&$oAuthTokenField=$oAuthToken&$oAuthVersionField=$oAuthVersion&$statusField=$status');
Honestly I don't know why this include_entities field creates an error during authentication. But yet in the documentation, include_entities=true is included in the step of creating a signature (oauth_signature)
So intuitively I removed this field because I didn't understand its role. And then everything started to work properly😁. Twitter developers should review their documentation and be more explicit about the usefulness of this include_entities field.
Thanks for everything