Auth error on statuses/update.json, but account/verify_credentials.json works fine


#1

I’m updating an app from v1 API to v1.1. Got the authentication working fine, and am able to do account/verify_credentials.json to confirm it. But I am unable to send a tweet using statuses/update.json.

This is Obj-C code using the GTMOAuth framework. After the authentication dance, this part works fine:

- (void) getTwitterInfo
{
	NSURL *url = [NSURL URLWithString: @"https://api.twitter.com/1.1/account/verify_credentials.json"];		
	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
	[myAuth authorizeRequest: request];
	GTMHTTPFetcher* myFetcher = [GTMHTTPFetcher fetcherWithRequest:request];	
	[myFetcher beginFetchWithCompletionHandler:^(NSData *retrievedData, NSError *error) 
	 {
		 if (error != nil) 
		 {
			 NSLog(@"StokerXTwitter Verification error: %@", error);
		 } 
		 else 
		 {
			 NSDictionary *results = [[[[NSString alloc] initWithData: retrievedData encoding:NSUTF8StringEncoding] autorelease] JSONValue];
			 self.twitterUserName = [results objectForKey: @"name"];
			 self.twitterHandle   = [results objectForKey: @"screen_name"];
			 NSLog(@"StokerXTwitter Verification Successful for %@ (@%@)", twitterUserName, twitterHandle);
			 [self sendTweet: @"StokerXTwitter Verification Successful"];
		 }
		 [self updateUI];
	 }];
}

2013-08-01 00:34:07 +0000 request/response log
request: GET https://api.twitter.com/1.1/account/verify_credentials.json
headers: 1 authorized
response: status 200
headers: 19 sets cookies
data: 1925 bytes, application/json

2013-08-01 00:34:07 +0000
Request: GET https://api.twitter.com/1.1/account/verify_credentials.json
Request headers:
Authorization: OAuth snip

Response: status 200
Response headers:
Cache-Control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0
Content-Encoding: gzip
Content-Length: 804
Content-Type: application/json;charset=utf-8
Date: Thu, 01 Aug 2013 00:34:07 GMT
Expires: Tue, 31 Mar 1981 05:00:00 GMT
Last-Modified: Thu, 01 Aug 2013 00:34:07 GMT
Pragma: no-cache
Server: tfe
Set-Cookie: lang=en, guest_id=v1%3A137531724768267057; Domain=.twitter.com; Path=/; Expires=Sat, 01-Aug-2015 00:34:07 UTC
status: 200 OK
strict-transport-security: max-age=631138519
x-access-level: read-write
x-frame-options: SAMEORIGIN
x-rate-limit-limit: 15
x-rate-limit-remaining: 13
x-rate-limit-reset: 1375317935
x-transaction: 8478566ed4c24a70
x-xss-protection: 1; mode=block

Response body: (1925 bytes)
{
…deleted…
}

This part fails:

- (void) sendTweet: (NSString *) tweet
{	
	if ([self isSignedIn])
	{		
		NSString *trimmedText = [tweet precomposedStringWithCanonicalMapping];
		
		if ([trimmedText length] > MAX_MESSAGE_LENGTH) {
			trimmedText = [trimmedText substringToIndex:MAX_MESSAGE_LENGTH];
		}
		
		NSString *body = [NSString stringWithFormat: @"status=%@", trimmedText];

		NSURL *url = [NSURL URLWithString: @"https://api.twitter.com/1.1/statuses/update.json"];
		NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
		[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
		[request setHTTPMethod:@"POST"]; 
		[request setHTTPBody: [body dataUsingEncoding:NSUTF8StringEncoding]];
		[myAuth authorizeRequest: request];
		
		GTMHTTPFetcher* myFetcher = [GTMHTTPFetcher fetcherWithRequest:request];	
		[myFetcher beginFetchWithCompletionHandler:^(NSData *retrievedData, NSError *error) 
		 {
			 if (error != nil) 
			 {
				 NSLog(@"StokerXTwitter sendTweet error: %@", error);
				 NSDictionary *results = [[[[NSString alloc] initWithData: retrievedData encoding:NSUTF8StringEncoding] autorelease] JSONValue];
				 NSLog(@"StokerXTwitter sendTweet results: %@", results);
			 }
		 }];
		
	}
}

2013-08-01 00:34:07 +0000 request/response log
request: POST https://api.twitter.com/1.1/statuses/update.json
headers: 3 authorized cookies
data: 45 bytes, application/x-www-form-urlencoded
response: status 401 ⚑
headers: 6
data: 63 bytes, application/json

2013-08-01 00:34:07 +0000
Request: POST https://api.twitter.com/1.1/statuses/update.json
Request headers:
Authorization: OAuth snip
Content-Type: application/x-www-form-urlencoded
Cookie: guest_id=v1%3A137531724768267057

Request body: (45 bytes)
status=StokerXTwitter Verification Successful

Response: status 401
Response headers:
Connection: close
Content-Encoding: gzip
Content-Type: application/json; charset=utf-8
Date: Thu, 01 Aug 2013 00:34:07 UTC
Server: tfe
strict-transport-security: max-age=631138519

Response body: (63 bytes)
{
“errors” : [
{
“message” : “Could not authenticate you”,
“code” : 32
}
]
}


#2

API v1.1 is a lot stricter about HTTP 1.1 and OAuth 1.0A – specifically around character encoding in query strings and POST bodies. You’ll need to make sure that before signing your request your parameter values are strictly encoded to RFC 3986 – not all libraries will do this for you or do it correctly.

Space characters should be encoded as “%20” for example. See http://tools.ietf.org/html/rfc3986#section-2 for more info.


#3

That fixed it. Changed one line:

NSString *body = [[NSString stringWithFormat: @"status=%@", trimmedText] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];