Unauthorized access error in POST request with content-type:application/json


#1

Hi,

I’m trying to send a post request to https://ads-api.twitter.com/1/tailored_audience_memberships endpoint, using TwitterJ4 Ads java library. I had to slightly modify it in order to be able to send post requests with JSON objects with application/json content-type header. However setting Content-Type: application/json causes UNAUTHORIZED_ACCESS error response. Below is relevant debug output:

2016-12-29 12:18:08 DEBUG [main] twitter4j.HttpClientImpl:52 - Request:
2016-12-29 12:18:08 DEBUG [main] twitter4j.HttpClientImpl:57 - POST https://ads-api.twitter.com/1/tailored_audience_memberships
2016-12-29 12:18:08 DEBUG [main] twitter4j.auth.OAuthAuthorization:57 - OAuth base string: POST&https%3A%2F%2Fads-api.twitter.com%2F1%2Ftailored_audience_memberships&media%3D%255B%257B%2522operation_type%2522%253A%2522Update%2522%252C%2522params%2522%253A%257B%2522advertiser_account_id%2522%253A%252218ce53x5rnx%2522%252C%2522user_identifier%2522%253A%2522E50E7DCF4F7756C9F279A933069E5AD48F8B985E49627A60681E6336D70E83DF%2522%252C%2522user_identifier_type%2522%253A%2522TWITTER_ID%2522%252C%2522audience_names%2522%253A%2522audienceId%2522%252C%2522effective_at%2522%253A%25222017-02-01T00%253A00%253A00Z%2522%252C%2522expires_at%2522%253A%25222017-02-05T00%253A00%253A00Z%2522%257D%257D%255D%26oauth_consumer_key%3DK8rpyfTzR7LPIynij3gBbuAII%26oauth_nonce%3D472202549%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1483006688%26oauth_token%3D87448941-Lk8UOA11TOrjicARS1HgLmyVJvOf1H2bx52hbVAP3%26oauth_version%3D1.0
2016-12-29 12:18:08 DEBUG [main] twitter4j.auth.OAuthAuthorization:57 - OAuth signature: 9VU8ij00mt488iWbiqYkl4vgUy8=
2016-12-29 12:18:08 DEBUG [main] twitter4j.HttpClientImpl:57 - Authorization: **********************************************************************************************************************************************************************************************************************************************************************************
2016-12-29 12:18:08 DEBUG [main] twitter4j.HttpClientImpl:52 - X-Twitter-Client-URL: http://twitter4j.org/en/twitter4j-4.0.5-SNAPSHOT.xml
2016-12-29 12:18:08 DEBUG [main] twitter4j.HttpClientImpl:52 - X-Twitter-Client: Twitter4J
2016-12-29 12:18:08 DEBUG [main] twitter4j.HttpClientImpl:52 - User-Agent: twitter4j http://twitter4j.org/ /4.0.5-SNAPSHOT
2016-12-29 12:18:08 DEBUG [main] twitter4j.HttpClientImpl:52 - X-Twitter-Client-Version: 4.0.5-SNAPSHOT
2016-12-29 12:18:08 DEBUG [main] twitter4j.HttpClientImpl:52 - Content-Type: application/json
2016-12-29 12:18:08 DEBUG [main] twitter4j.HttpClientImpl:57 - Post Params: media=%5B%7B%22operation_type%22%3A%22Update%22%2C%22params%22%3A%7B%22advertiser_account_id%22%3A%2218ce53x5rnx%22%2C%22user_identifier%22%3A%22E50E7DCF4F7756C9F279A933069E5AD48F8B985E49627A60681E6336D70E83DF%22%2C%22user_identifier_type%22%3A%22TWITTER_ID%22%2C%22audience_names%22%3A%22audienceId%22%2C%22effective_at%22%3A%222017-02-01T00%3A00%3A00Z%22%2C%22expires_at%22%3A%222017-02-05T00%3A00%3A00Z%22%7D%7D%5D
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - Response:
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - HTTP/1.1 401 Authorization Required
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - x-frame-options: SAMEORIGIN
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - content-type: application/json;charset=utf-8
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - x-response-time: 9
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - date: Thu, 29 Dec 2016 10:18:09 GMT
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - x-transaction: 0067312d0064d7d0
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - x-connection-hash: 4181e8f48eaa139d3d5141112b43cccf
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - x-xss-protection: 1; mode=block
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - x-content-type-options: nosniff
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - set-cookie: guest_id=v1%3A148300668968413810; Domain=.twitter.com; Path=/; Expires=Sat, 29-Dec-2018 10:18:09 UTC
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - content-length: 122
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - content-disposition: attachment; filename=json.json
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - server: tsa_b
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - strict-transport-security: max-age=631138519
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpClientImpl:52 - x-tsa-request-body-time: 6
2016-12-29 12:18:09 DEBUG [main] twitter4j.HttpResponseImpl:52 - {“request”:{“params”:{}},“errors”:[{“code”:“UNAUTHORIZED_ACCESS”,“message”:“This request is not properly authenticated”}]}

Sending the same request via twurl works fine, so this is a problem in the client code. However it’s unclear why setting up the header would cause authentication problem.

I’d appreciate any help that could point me to a right direction.


#2

Thanks for this information, @Andrey. Good to hear you’re using Twitter4J. @abhishek_pyro, any thoughts on this?


#3

Hey @juanshishido and @Andrey, Happy New years :slight_smile:

Looking at the API this seems to be a batch end point, the support for batch end points is not present in the current Http Client in Twitter4jAds, this is because we use the Twitter4j library for its client.

We are adding our own client for this coming release (which has been delayed quite a bit, apologies for that).

That could be one reason for the error.

@Andrey could you help me with the constructor of the Http Post request that you are using in the code. Will be happy to connect on email as well if you’re more comfortable (abhishekanand100@gmail.com).

Cheers


#4

Hi @abhishek_pyro and @juanshishido ! Happy New year too!

Here is a code snippet I added to build the request inside Twitter4JAds:

public BaseAdsResponse updateMembership(String advertiserAccountID, Collection userIDs, String audienceNames, Date effectiveAt, Date expiresAt) throws TwitterException {

    String baseUrl = twitterAdsClient.getBaseAdsAPIUrl() + "1/tailored_audience_memberships";
    List<HttpParameter> params = new ArrayList<>();
    List<TailoredAudienceMembership> list = buildMembershipBatch(advertiserAccountID, userIDs, audienceNames, effectiveAt,expiresAt);
    final String json = listToJson(list);
    params.add(new HttpParameter("media", json));
    Map<String, String> headers = new HashMap<>();
    headers.put("Content-Type", "application/json");
    Type type = new TypeToken<BaseAdsResponse<TailoredAudienceMembershipUpdate>>() {}.getType();
    return twitterAdsClient.executeHttpRequest(baseUrl, params.toArray(new >HttpParameter[params.size()]), type, HttpVerb.POST, headers);

}

Then the headers Map is merged into the request headers in HttpClientImpl:

public HttpResponse post(String url, HttpParameter[] parameters
, Authorization authorization, HttpResponseListener listener, Map<String,String> headers) throws TwitterException {

	Map<String,String> mergedHeaders = null;
	if (headers != null) {
		mergedHeaders = new HashMap<String, String>(this.requestHeaders);
		mergedHeaders.putAll(headers);
		
	} else {
		mergedHeaders = this.requestHeaders;
	}
    return request(new HttpRequest(RequestMethod.POST, url, parameters, authorization, mergedHeaders), listener);
}

Then in HttpClientImpl.handleRequest(HttpRequest) I had to add this:

                    	if (con.getRequestProperty("Content-Type") == null) {
                    		con.setRequestProperty("Content-Type",
                    				"application/x-www-form-urlencoded");
                    	}

in order to avoid forced “application/x-www-form-urlencoded” content-type.

But this somehow breaks the authentication.

Do you spot any problems with these amendments?

Thanks


#5

I think your API path is wrong - instead of using + “1/tailored_audience_memberships” you should do something like twitterAdsClient.getBaseAdsAPIUrl() + TwitterAdsConstants.PREFIX_ACCOUNTS_V1 + accountId + TwitterAdsConstants.

(this turns into /1/accounts//tailored_audience_memberships instead of /1/tailored_audience_memberships)


#6

Hi @JBabichJapan

The endpoint URL is https://ads-api.twitter.com/1/tailored_audience_memberships as described in
https://dev.twitter.com/ads/reference/post/tailored_audience_memberships .

Yet I tried your suggestion and got ROUTE_NOT_FOUND error.


#7

Hey @Andrey

Yes, these are the changes that you will need to do

Expose a method

public YourResponse methodName(String accountId, List<TargetingParamRequest> targetingParamRequests)
        throws TwitterException {
    String baseUrl = twitterAdsClient.getBaseAdsAPIUrl() + PREFIX_BATCH_URI_1 + PATH_ACCOUNTS + accountId + PATH_TARGETING_CRITERIA;
    HttpResponse httpResponse = twitterAdsClient.postBatchRequest(baseUrl, GSON.toJson(targetingParamRequests));
    Type typeToken = new TypeToken<TargetingParamResponse>() {}.getType();
    return GSON.fromJson(httpResponse.asString(), typeToken);
}

You will need to add a field in HttpRequest
private final String requestBody; (this is where your post batch request JSON goes)

The HttpClientImpl class gets a little modified with the request method being

public HttpResponse request(HttpRequest req) throws TwitterException {

                    } else if (req.getRequestBody() != null) {
                        con.setRequestProperty("Content-Type", "application/json");
                        String payload = req.getRequestBody();
                        logger.debug("Payload: ", payload);
                        byte[] bytes = payload.getBytes("UTF-8");
                        con.setRequestProperty("Content-Length", Integer.toString(bytes.length));
                        con.setDoOutput(true);
                        os = con.getOutputStream();
                        os.write(bytes);
                   
}

If you see the request body handling is done in the method which adds the required header in the request.

This is the change which will go in the next week’s coming release, we are trying to push the change sooner looking at the issue you are running into.

Thanks


#8

Whoops sorry about that, I didn’t realize the endpoint you are talking about it since we don’t support it in the other SDKs yet. It’s great to support this in Java earlier than the other ones so I would encourage you to submit it as a Pull Request :slight_smile:


#9

Hi

I applied your suggestions and it works now! Thanks a lot!!!

@JBabichJapan no problem :slight_smile: At the moment it’s difficult to submit these changes as there are a few modifications in dependent Twitter4J library. I’ll have to wait for @abhishek_pyro to make a release first with the above fixes :slight_smile: