Android app connecting to shutterstock api throws IOException with Error 401 - java

I am trying to connect my android app to shutterstock api so that it can search for some images there. It uses https scheme + Basic Authentication header to allow users for all search requests. I implemented the functionality in a regular java project using HttpsURLConnection and was able to get correct JSON responses.
The java code looks like this:
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();//proxy);
String username = "62c01aa824222683004b", password = "dc4ad748a75e4e69ec853ad2435a62b700e66164";
String encoded = Base64.getEncoder().encodeToString((username+":"+password).getBytes("UTF-8"));
System.out.println(encoded.equals("Nj0jMDFhZWE4ZmE4MjY4MzAwNGI6ZGM0YWQ3NDhhNzVlNGU2gWVjODUzYWQ0ZmEzYTYyYjc7MGU2NjE2NA==")); // prints true
urlConnection.setRequestProperty("Authorization", "Basic "+encoded);
When ported this into Android, it was throwing an IOException with 401 error code. As explained in many posts on SO (like the one here), I modified the code accordingly with an extra try-catch as below:
String username = "62c01aa824222683004b", password = "dc4ad748a75e4e69ec853ad2435a62b700e66164", encoded = "";
encoded = Base64.encodeToString((username+":"+password).getBytes("UTF-8"), Base64.URL_SAFE);
Log.e("test", "encoded strings match:" + encoded.equals("Nj0jMDFhZWE4ZmE4MjY4MzAwNGI6ZGM0YWQ3NDhhNzVlNGU2gWVjODUzYWQ0ZmEzYTYyYjc7MGU2NjE2NA==") + "\n" + encoded); // prints false but string is same!!
URL url = new URL(reqUrl);
connection = (HttpsURLConnection) url.openConnection();
connection.setRequestProperty("Authorization", "Basic "+encoded);
try {
if (connection != null) {
connection.connect();
if (200 == connection.getResponseCode()) { // ---> throws IOException
BufferedReader br = new BufferedReader(new InputStreamReader((connection.getInputStream())));
String output;
while ((output = br.readLine()) != null) {
Log.e("test", output);
response.append(output);
}
connection.disconnect();
return response.toString();
}
}
} catch (IOException e) {
try {
Log.e("test", e.getMessage()); // ---> prints "No authentication challenges found"
Log.e("test", connection.getResponseCode() + ":" + connection.getResponseMessage() + connection.getHeaderFields());
//---> prints 401:Unauthorized{null=[HTTP/1.1 401 Unauthorized], cache-control=[no-cache], Connection=[keep-alive], Content-Length=[38], Content-Type=[application/json; charset=utf8], Date=[Tue, 31 May 2016 14:11:28 GMT], Server=[nginx], X-Android-Received-Millis=[1464703888222], X-Android-Sent-Millis=[1464703887592], x-end-user-request-id=[f754ec7f-c344-431b-b641-360aabb70184], x-shutterstock-app-version=[apitwo-625], x-shutterstock-resource=[/v2/images/search]}
if (401 == connection.getResponseCode()) {
InputStream es = connection.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(es));
String output;
while ((output = br.readLine()) != null) {
Log.e("test", output); // ---> prints {"message":"Invalid auth credentials"}
response.append(output);
}
connection.disconnect();
return response.toString();
} else {
Log.e("test","Could not connect! " + connection.getResponseCode() + ":" + connection.getResponseMessage() + ". " + connection.getRequestMethod());
}
}catch (Exception e1){e1.printStackTrace();}
}
I was unable to check the response headers in Firefox's Rest client because it does not send the request to server when I add the Authentication header.
So the questions here are:
Is this the right way to handle the 401 error in Android? Will I get the JSON response in the inner try-catch?
The java program uses exactly the same encoded string as in Android. How come the String.equals() returns "true" in java but "false" in android?
The error message from the server says "Invalid auth credentials". Does the encoded string differ between Android and Java for any reason? If yes, then point 2 makes sense.

I copied the encoded string from the java program into the Android variable and was able to authenticate successfully with shutterstock. So Indeed the encoded strings on Android and Java were different though in UTF-8 format. This also explains the "false" in Android and the "Invalid credentials" message from the server.
Just not sure why/how it differs when both the encoded strings are the same for the human eyes!

Related

Microsoft Graph: Requesting an Extension returns http 400 bad request

I added an open extension to an event in a calendar and am trying to read it back.
Here is the url:
https://graph.microsoft.com/v1.0/users/{userid}/calendars/{calendarId}=/events?$expand=Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event')
I cannot get this to work in a Java program. The following combinations do work:
It works my Java program if I remove the $expand... parameter. I can also ask for certain fields, that works too.
The request works in Postman (I just have to set the token)
The request works in Graph Explorer when I log in as the owner of the calendar
Here is the extension (inside one of the events) when I use Postman to read the event. It is the last item in the event:
"extensions#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('{userid}')/calendars('{calendarId}')/events('{eventId})/extensions",
"extensions": [
{
"#odata.type": "#microsoft.graph.openTypeExtension",
"id": "Microsoft.OutlookServices.OpenTypeExtension.c.i.m.p.server.entities.outlook.Event",
"extensionName": "c.i.m.p.server.entities.outlook.Event",
"adherentId": "12346",
"timeSlotID": "346463"
}
]
Here is the Java code (Java 8, using java.io and java.net libraries):
private static void doSomething(String _accessToken) throws IOException {
String urlString = "https://graph.microsoft.com/v1.0/users/{userId}/calendars/{calendarId}/events?$expand=Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event')";
URL url = new URL(urlString);
Proxy webProxy
= new Proxy(Proxy.Type.HTTP, new InetSocketAddress({proxy-address}, {port}));
HttpURLConnection connection = (HttpURLConnection) url.openConnection(webProxy);
// Set the appropriate header fields in the request header.
connection.setRequestProperty("Authorization", "Bearer " + _accessToken);
connection.setRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
connection.setReadTimeout(5000);
connection.setRequestMethod(HttpMethod.GET);
try {
connection.connect();
int responseCode = connection.getResponseCode();
System.out.println("execute(), response code = " + responseCode);
String responseMessage = connection.getResponseMessage();
System.out.println("execute(), response Message = " + responseMessage);
String responseString = null;
try {
InputStream ins = connection.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(ins));
StringBuffer sb=new StringBuffer();
String line;
while ((line=br.readLine()) != null) {
sb.append(line);
}
responseString = sb.toString();
} catch (Exception e) {
System.out.println("Could not get input stream from response, error is " + e.toString());
}
System.out.println("execute(), httpResult = " + responseString);
} catch (IOException e) {
System.out.println(".execute(), IOException : " + e.toString());
} finally {
connection.disconnect();
}
}
How do I fix this? Thanks!
400 means bad request. It could be because of url encoding. Url encode the query string.
Something like
String query = "Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event'";
String url = "https://graph.microsoft.com/v1.0/users/{userId}/calendars/{calendarId}/events?
$expand=" + URLEncoder.encode(query, StandardCharsets.UTF_8.name());
Alternatively you could use graph service java api based on your need which will help abstract all the interactions for you or you could use any of the rest clients available.
First of all, you should provide more info on the error - Stacktrace and error message. But 400 code indicates that was a user mistake, meaning that you are sending an invalid request. Since you say that postman request works then compare all the headers that are sent by postman and see if your code misses some hearer. As for the code, instead of coding your own Http client functionality I would suggest using 3d party Http client. Here are a few suggestions:
Apache Http client - very popular and well known 3d party Http Client
OK Http client - Open-source Http client. Here is tutorial
MgntUtils Http client - very simple 3d party HttpClient: Provided in MgntUtils Open source library (written by me). Very simple in use. Take a look at Javadoc. Library itself provided as Maven artifacts and on Git (including source code and Javadoc).

I have given correct parameters and headers for POST method, but response code is 400

I was trying to integrate SnapChat login kit to my web application. I was able to get the authorization code from the SnapChat, but I am having trouble getting the access token.
I have done FB, Google, Instagram login integration perfectly fine, but this one I am totally lost.
I have tried to encode the parameters, change the order of headers, but all the time it gives me 400 bad request.
try {
URL url = null;
if(service.equals(APIConstants.GOOGLE))
url = new URL(SNSIdentifications.GOOGLE_TOKEN_REQUEST_URL);
else if (service.equals(APIConstants.FACEBOOK))
url = new URL(SNSIdentifications.FACEBOOK_TOKEN_REQUEST_URL);
else if (service.equals(APIConstants.INSTAGRAM))
url = new URL(SNSIdentifications.INSTAGRAM_TOKEN_REQUEST_URL);
else
url = new URL(SNSIdentifications.SNAPCHAT_TOKEN_REQUEST_URL);
conn = (HttpsURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
if(service.equals(APIConstants.SNAPCHAT)) {
// String userCredentials = URLEncoder.encode(client_id + ":" + secret, "UTF-8");
String userCredentials = client_id + ":" + secret;
byte[] data = Base64.getEncoder().encode(userCredentials.getBytes());
String authorizationHeaderBase64 = new String(data);
conn.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded" );
conn.setRequestProperty( "Authorization", "Basic " + authorizationHeaderBase64);
// conn.setRequestProperty("charset", "utf-8");
//Integer.toString(payload.getBytes(StandardCharsets.UTF_8).length)
// conn.setRequestProperty( "Content-Length", Integer.toString(payload.getBytes(StandardCharsets.UTF_8).length) );
writer = new OutputStreamWriter(conn.getOutputStream());
// writer.write(URLEncoder.encode(payload, "UTF-8"));
writer.write(params);
writer.flush();
System.out.println(conn.getResponseCode() + " " + conn.getResponseMessage() + " ");
} else {
writer = new OutputStreamWriter(conn.getOutputStream());
writer.write(params);
writer.flush();
}
int responseCode = conn.getResponseCode();
if(responseCode == HttpURLConnection.HTTP_OK) {
return new JSONObject(fetchResponse(conn));
}
} catch (IOException e) {
e.printStackTrace();
}
I should expect access token, refresh token, and expires_in values from the code above, but unfortunately it cannot, because of 400 bad request error.
I solved the problem. It turned out it was several combinations of problems.
When you are having an issue as I posted, please do check the followings:
Make sure you are using correct Client Id and Secret.
If you are adding Code Challenge and code challenge method parameters, you need to have code verifier to get access token. If you don't have these parameters, you don't need to worry about getting an access token.
When setting a header "Authorization" use base64 encode, not base16. (Snapchat page says base16, but in an example, it's in 64).
Check your urls, id, secret once again with those to get the authorization code.
:)

Google Drive Api OAuth2 no token but lots of errors

I am trying to access the Drive Api from a JavaFX Desktop app. After I received the initial authentication (aka code), I want to get the token to be able to work with the api. The config is set up as a native application in the dev console.
When I attempt to fetch the token I get various http-errors (400, 401, 404, 405, 406, 411). I guess something is wrong with the encoding and/or the way I send the request to the server.
Here the code:
public void sendOAuth2TokenRequest(String initialOAuth2) {
final String charset = "UTF-8";
try {
String url = "https://www.googleapis.com/oauth2/v3/token";
HttpsURLConnection con = (HttpsURLConnection) new URL(url).openConnection();
con.setDoOutput(true);
con.setRequestMethod("POST");
con.setRequestProperty("Accept-Charset", charset);
con.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
OutputStream output = con.getOutputStream();
output.write(getEncodedUrlParms().getBytes(charset));
output.close();
BufferedReader responseReader =
new BufferedReader(new InputStreamReader(con.getInputStream(), charset));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = responseReader.readLine()) != null) {
response.append(inputLine);
}
Log.i(TAG, "OAuth2Request " + inputLine);
responseReader.close();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "OAuth2Request Error: " + e.getMessage());
}
}
private String getEncodedUrlParms() throws UnsupportedEncodingException {
return "code=" + URLEncoder.encode(getInitialCode(), "UTF-8") +
"&client_id=" + URLEncoder.encode(DriveHelper.CLIENT_ID, "UTF-8") +
"&client_secret=" + URLEncoder.encode(DriveHelper.CLIENT_SECRET, "UTF-8") +
"&redirect_uri=" + URLEncoder.encode("urn:ietf:wg:oauth:2.0:oob", "UTF-8") +
"&grant_type=" + URLEncoder.encode("authorization_code", "UTF-8");
}
Please let me know what the mean here:
The client secret you obtained from the Developers Console (optional for clients registered as Android, iOS or Chrome applications).
Is the client_secret needed (mandatory) in native apps?

REQUEST DENIED with Google Places API KEY for Server Web used in an Android application

I'm trying to get info from the API of Google Places for an Android application. To do that, first I have enabled this API in my Google Account.
Second, I have created an API KEY for Browser. I already have an API KEY Server due to another API.
So, in my code I have been tested with these two Keys and with both I've got always the same result!!!
{
"error_message" : "This service requires an API key.",
"html_attributions" : [],
"results" : [],
"status" : "REQUEST_DENIED"
}
The code that I'm using to make the call are ...
#Override
protected String doInBackground(LocationService... ls) {
JSONObject result = new JSONObject();
URL url;
HttpsURLConnection urlConnection;
// Making HTTP request
try {
//Define connection
url = new URL("https://maps.googleapis.com/maps/api/place/nearbysearch/json");
urlConnection = (HttpsURLConnection)url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("charset", "utf-8");
urlConnection.setRequestProperty("Accept", "application/json");
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
urlConnection.setUseCaches(false);
//Send data
String parameters = "?location=" + String.valueOf(ls[0].getLocation().getLatitude()) + "," + String.valueOf(ls[0].getLocation().getLongitude());
parameters+="&radius=5000";
parameters+="&types=restaurant|health|city_hall|gas_station|shopping_mall|grocery_or_supermarket";
parameters+="&sensor=false";
parameters+="&key=" + Constants.API_KEY_BROWSER_APPLICATIONS;
byte[] postData = parameters.getBytes(Charset.forName("UTF-8"));
int postDataLength = postData.length;
urlConnection.setRequestProperty("Content-Length", Integer.toString(postDataLength));
DataOutputStream data = new DataOutputStream(urlConnection.getOutputStream());
data.write(postData);
data.flush();
data.close();
Log.d(TAG, "Datos enviados");
Log.d(TAG, "ResponseCode: " + String.valueOf(urlConnection.getResponseCode()));
//Display what returns POST request
StringBuilder sb = new StringBuilder();
int HttpResult = urlConnection.getResponseCode();
if(HttpResult == HttpURLConnection.HTTP_OK){
String json;
BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(),"utf-8"));
String line;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
//System.out.println(""+sb.toString());
Log.d(TAG, "json: " + sb.toString());
FileService file = new FileService();
file.writeLog(POIActivity.TAG, getClass().getName(), POIActivity.urlConnection + parameters);
file.writeLog(POIActivity.TAG, "doInBackground", sb.toString());
// Parse the String to a JSON Object
result = new JSONObject(sb.toString());
}else{
//System.out.println(urlConnection.getResponseMessage());
Log.d(TAG, "urlConnection.getResponseMessage(): " + urlConnection.getResponseMessage());
result = null;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
Log.d(TAG, "UnsuppoertedEncodingException: " + e.toString());
} catch (JSONException e) {
e.printStackTrace();
Log.d(TAG, "Error JSONException: " + e.toString());
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "IOException: " + e.toString());
}
// Return JSON Object
return result.toString();
}
When I make the call to the API I've got like ResponseCode = 200 and the call that I build is finally like that ...
https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=38.26790166666667,-0.7052183333333333&radius=5000&types=restaurant|health|city_hall|gas_station|shopping_mall|grocery_or_supermarket&sensor=false&key=API_KEY
Remember, like API_KEY I have used both, Api Key for server applications and Api Key for browser applications and I've got the same result with both.
Sincerely, I'm desperate with this problem because I don't know what I am doing wrong!!!
The problem is that you are not using the Google Places API for Android, you are using the Google Places API Web Service.
Here is an example of using Google Places API for Android, and here is an example of using the Google Places API Web Service. You are definitely using the latter.
Enable the Google Places API Web Service and it will work:
If you go to this link while signed into your Google Cloud Console account:
https://console.cloud.google.com/apis/library?filter=category:maps
This is the API that should be enabled:
From the documentation:
Note: You need an Android API key, not a browser key. You can use the same API key for your Google Maps Android API v2 apps and your Google Places API for Android apps.
Check this for more help.
For an easier way try latest GCM configuration file implementation and easily create project using their developer interface.
Enable Google services for your app

HTTP Basic Authentication Java

I am trying to get content from the website Socialcast which needs authentication. (First I do a HTTP Post with Basic Authentication and then I try a HTTP GET).
I tried several codes, I receive this as "result":
emily#socialcast.com:demo
Base64 encoded auth string: ZW1pbHlAc29jaWFsY2FzdC5jb206ZGVtbw==
* BEGIN
You are being redirected.
END *
Here is the code for HTTP Basic Auth:
try {
String webPage = "http://demo.socialcast.com";
String name = "emily#socialcast.com";
String password = "demo";
String authString = name + ":" + password;
System.out.println("auth string: " + authString);
byte[] authEncBytes = Base64.encodeBase64(authString.getBytes());
String authStringEnc = new String(authEncBytes);
System.out.println("Base64 encoded auth string: " + authStringEnc);
URL url = new URL(webPage);
URLConnection urlConnection = url.openConnection();
urlConnection.setRequestProperty("Authorization", "Basic " + authStringEnc);
InputStream is = urlConnection.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
int numCharsRead;
char[] charArray = new char[1024];
StringBuffer sb = new StringBuffer();
while ((numCharsRead = isr.read(charArray)) > 0) {
sb.append(charArray, 0, numCharsRead);
}
String result = sb.toString();
System.out.println("*** BEGIN ***");
System.out.println(result);
System.out.println("*** END ***");
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
However, when I try to do a GET afterwards, it says unauthorized.
The credentials are emily#socialcast.com/demo - those are provided by Socialcast Dev at the moment, as I also cannot access my own Socialcast instance.
Is this code wrong? How can I do it properly? BTW, I am using HttpClient 4.x.
Are you sending the credentials in each request? I think this is needed, otherwise the server does not have any other information to prove that you still are authorized to view other pages...
I'm not sure why this question is tagged with apache-httpclient-4.x when your example code doesn't use it. In fact, if you do use httpclient then you can get it to handle authentication for you quite easily, see here for the excellent tutorial.

Categories