I am using google books API KEY for android to load book data via my android application. But I am getting error listed below.
However,
- The query works if I delete certificates from my API CONSOLE (e.g. make API KEY acceptable for all Applications). Though my {SHA1};{package name} information that I put is correct.
- This works if I use API KEY for browser instead.
Hence, what I can understand, I can't send KEY as a part of url in httpclient method. May be I need to send via header or something. But I can't find how.
Anybody can help please?
CODE:
String link = "https://www.googleapis.com/books/v1/volumes?key=MY_KEY&q="+query+"&projection=full&langRestrict=en&maxResults=1";
HttpGet get = new HttpGet(link);
HttpParams httpParameters = new BasicHttpParams();
int timeoutConnection = 10000;
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutConnection);
HttpClient httpclient = new DefaultHttpClient(httpParameters);
HttpResponse response = httpclient.execute(get);
HttpEntity entity = response.getEntity();
Query:
https://www.googleapis.com/books/v1/volumes?key=MY_API_KEY&q=intitle:'The+Old+Man+And+The+Sea'+inauthor:'Ernest+Hemingway'&projection=full&langRestrict=en&maxResults=1
Result:
{
"error": {
"errors": [
{
"domain": "usageLimits",
"reason": "accessNotConfigured",
"message": "Access Not Configured"
}
],
"code": 403,
"message": "Access Not Configured"
}
}
For whom it may concern ... I send my API key in header, and it is now working.. I am not sure whether this is the proper way to it though ...
String link = "https://www.googleapis.com/books/v1/volumes?q="+params;
InputStream is = null;
try
{
int timeoutConnection = 10000;
URL url = new URL(link);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(timeoutConnection);
con.setReadTimeout(timeoutConnection);
con.setRequestProperty("key", "API_KEY");
if(con.getResponseCode() != HttpURLConnection.HTTP_OK){
publishProgress("Error conneting.");
}
is=con.getInputStream();
}
Supplying the key in the header is the same as not supplying a key at all. Check your developer api console and you will see no successful queries with that api key. So in short the API key in header does not solve the problem. If you don't want to play by google's rules just do the query without the key at all.
You can achieve the same result of the accepted answer using HttpGet:
String API_KEY = "..."; // Google Books API Public key
String ISBN = "...";
String bookSearchString = "https://www.googleapis.com/books/v1/volumes?q=isbn:" + ISBN;
HttpClient bookClient = new DefaultHttpClient();
try
{
//get the data
HttpGet bookGet = new HttpGet(bookSearchURL);
bookGet.addHeader("key", API_KEY);
HttpResponse bookResponse = bookClient.execute(bookGet);
StatusLine bookSearchStatus = bookResponse.getStatusLine();
if (bookSearchStatus.getStatusCode() == 200)
{
//we have a result
HttpEntity bookEntity = bookResponse.getEntity();
...
}
} catch (Exception e)
{
e.printStackTrace();
}
Note that using Google API key without restrictions is insecure way to do!
Obviously if you’re being charged for access to the API data then you don’t want someone to decompile your APK, find your API key, and then start using it in their own apps.
How to hide your API Key in Android?
But It's not enough. You can store your API key somewhere, so someone can find it too.
To secure Google cloud API key for your android app, you must restrict it to use from your app only. See here for more.
After restrict key, you must include your android app package name and SHA-1 certificate fingerprint in the header of each request you are sending to Google. How to do?
That's all you need ! :D
Related
I know how it can be done for Google Play Store (answer here), but i didn't manage to find a way for AppGallery.
Thank you!
UPDATE #1
Using the answer below i partially solve this with this steps:
Make an API Client with Administrator role, it also works with App Administrator and Operations. (documentaion here: API Client)
Get the access token. (documentaion here: Obtaining a Token)
Get app info. (documentaion here: Querying App Information)
The response from the Querying App Information, have a lot of informations about the app including "versionNumber", but for me it doesn't provide the "versionNumber" (the single info i needed), because this parameter is optional. And now i am stuck again, because i don't understand what i need to change in order to receive this one.
If anyone knows how I can solve this, thank you very much for your help.
UPDATE #2
#shirley's comment was right.
The issue has been fixed in their latest release, and it has been released this month.
You can call the Querying App Information API (GET mode) to query the app details:
public static void getAppInfo(String domain, String clientId, String token, String appId, String lang) {
HttpGet get = new HttpGet(domain + "/publish/v2/app-info?appid=" + appId + "&lang=" + lang);
get.setHeader("Authorization", "Bearer " + token);
get.setHeader("client_id", clientId);
try {
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse httpResponse = httpClient.execute(get);
int statusCode = httpResponse.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
BufferedReader br =
new BufferedReader(new InputStreamReader(httpResponse.getEntity().getContent(), Consts.UTF_8));
String result = br.readLine();
// Object returned by the app information query API, which can be received using the AppInfo object. For details, please refer to the API reference.
JSONObject object = JSON.parseObject(result);
System.out.println(object.get("ret"));
}
} catch (Exception e) {
}
}
They are mentioned here: Completing App Information, Querying App Information.
Is it possible to restrict the Time Zone API key to Android Apps?
Google Maps Android API and Google Places API keys can be restricted to certain Androids Apps by defining the package name and SHA-1 hash.
This works without problem for the Maps and Places API, but using the exact same settings for the Time Zone API constantly returns "REQUEST_DENIED". Setting the application restrictions back to "None" results in a successful query, but I don't want to leave my API key unprotected.
The way I'm calling the API is as follows:
double lat = 51.1789;
double lon = -1.8262;
long time = 1523092938;
String Api_key = "#API_KEY#";
String query = "https://maps.googleapis.com/maps/api/timezone/json?location="+String.format(Locale.ROOT, "%.4f",lat)+","+String.format(Locale.ROOT, "%.4f",lon)+"×tamp="+time+"&key="+ Api_key;
protected JSONObject doInBackground(String... params) {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL(query);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.connect();
System.out.println(query);
InputStream stream = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(stream));
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null) {
buffer.append(line+"\n");
Log.d("Response: ", "> " + line);
}
return new JSONObject(buffer.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
The received response is:
{
"errorMessage" : "This IP, site or mobile application is not authorized to use this API key. Request received from IP address XX.XX.XX.XX, with empty referer",
"status" : "REQUEST_DENIED"
}
If I set the Application restriction to None, everything works:
{
"dstOffset" : 3600,
"rawOffset" : 0,
"status" : "OK",
"timeZoneId" : "Europe/London",
"timeZoneName" : "British Summer Time"
}
I've found out that this is essentially the same issue as described here: Android Google Maps Direction Api - Api key restriction not working
In short, Google Maps Android API is a webservice and the only API key restrictions that work are IP restrictions (App restrictions work for Android APIs only).
So you're left with only one solution: Implement an intermediate server that receives queries from your app, queries the Google API, and responds to your app by passing the Google API response. In this way it's possible to restrict the API key to your intermediate servers IP.
However, how you restrict the access to your intermediate server against third parties is a question on its own.
So I'm trying to use Google URL Shortener API in my app. Here's the class I wrote to make the HTTP call and retrieve the shortened URL.
public class GoogleUrlShortnerApi
{
//API Key from Google
private const string key = "-----------MY_KEY-----------";
public static string Shorten(string url)
{
string post = "{\"longUrl\": \"" + url + "\"}";
string shortUrl = url;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://www.googleapis.com/urlshortener/v1/url?key=" + key);
try {
request.ServicePoint.Expect100Continue = false;
request.Method = WebRequestMethods.Http.Post;
request.ContentLength = post.Length;
request.ContentType = "application/json";
request.Headers.Add("Cache-Control", "no-cache");
using (Stream requestStream = request.GetRequestStream())
{
byte[] postBuffer = Encoding.ASCII.GetBytes(post);
requestStream.Write(postBuffer, 0, postBuffer.Length);
}
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (Stream responseStream = response.GetResponseStream())
{
using (StreamReader responseReader = new StreamReader(responseStream))
{
string json = responseReader.ReadToEnd();
shortUrl = Regex.Match(json, #"""id"": ?""(?<id>.+)""").Groups["id"].Value;
}
}
}
} catch (WebException webEx) {
System.Diagnostics.Debug.WriteLine (webEx.Message);
string responseText;
using(var reader = new StreamReader(webEx.Response.GetResponseStream()))
{
responseText = reader.ReadToEnd();
}
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine (ex.Message);
}
return shortUrl;
}
}
But I keep getting the "The remote server returned an error: (403) Forbidden." error.
I tried to debug and put a breakpoint on the 2nd using in the class..
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
It never goes inside that using and catches a WebException.
Can anyone give me an idea on what I'm doing wrong here?
Thank you for your time.
========================= UPDATE =========================
This is the value of the responseText from the WebException. I'm allowed to make 1,000,000 request per day. Why am I getting this error?
{
"error": {
"errors": [
{
"domain": "usageLimits",
"reason": "ipRefererBlocked",
"message": "There is a per-IP or per-Referer restriction configured on your API key and the request does not match these restrictions. Please use the Google Developers Console to update your API key configuration if request from this IP or referer should be allowed.",
"extendedHelp": "https://console.developers.google.com"
}
],
"code": 403,
"message": "There is a per-IP or per-Referer restriction configured on your API key and the request does not match these restrictions. Please use the Google Developers Console to update your API key configuration if request from this IP or referer should be allowed."
}
}
I figured it out!!
The key I created was for an Android device and it kept giving me that error.
So after realizing that it is IP issue, I created a SERVER KEY because no other key has IP option. I put the server key in my app and BOOM! it worked!
There is a per-IP or per-Referer restriction configured on your API key and the request does not match these restrictions. Please use the Google Developers Console to update your API key configuration if request from this IP or referer should be allowed.
I read that as your API key is setup to be restricted by IP and your request is coming from an IP that is not registered to use that API key. Keep in mind that requests from the emulator will (most likely) have a different IP than the machine they are running on, because the Android emulator is a separate VM.
Either figure out the IP that the request is originating from and register it with your API key, or (if possible) switch the restriction to be per-Referer and handle that in your code.
I am trying to create a image upload module Using Imgur API
I have just got Client ID and Client Secret after registration. When it comes to the implementation and testing, it fails and gives the following response in the logcat
The Logcat response
{"data":{"error":"We're really sorry, but
anonymous uploading in your country has
been disabled. Please <a href=\"\/register\">register
for an account<\/a> and try uploading again.","request":"\/3\/upload.json","method":"POST"}
,"success":false,"status":400}
The below is my code
public String uploadToImgur(File uploadFile) {
DefaultHttpClient defaulthttpclient;
HttpPost httppost;
MultipartEntity multipartentity;
String path = uploadFile.getAbsolutePath().toString();
String s;
defaulthttpclient = new DefaultHttpClient();
String targetURL = "https://api.imgur.com/3/upload.json";
String apikey = "client_secret";
httppost = new HttpPost(targetURL);
httppost.setHeader("User-Agent", USER_AGENT);
httppost.addHeader("Authorization", "Client-ID {client)_id}");
multipartentity = new MultipartEntity();
s = path.substring(1 + path.lastIndexOf("."));
if (s.lastIndexOf("jpg") >= 0)
{
s = "jpeg";
}
try
{
multipartentity.addPart("image", new FileBody(new File(path), (new StringBuilder("image/")).append(s).toString()));
multipartentity.addPart("key", new StringBody(apikey));
httppost.setEntity(multipartentity);
String s1 = EntityUtils.toString(defaulthttpclient.execute(httppost).getEntity());
Log.d("outpur" , s1);
if (s1.lastIndexOf("<original>") >= 0 && s1.indexOf("</original>") >= 0)
{
return (new StringBuilder("[img]")).append(s1.substring(10 + s1.lastIndexOf("<original>"), s1.indexOf("</original>"))).append("[/img]").toString();
}
}
catch (Exception exception)
{
return "ERRor";
}
return "Error";
}
Would you please tell me what is the better way to enhance the upload module ?
Registration and sending client id is not good enough for non anonymous uploads. The documentation tells you to use oAuth and get a token that needs to be passed for such requests.
Authentication
The API requires each client to use OAuth 2 authentication. This means you'll have to register your application, and generate an access_code if you'd like to log in as a user.
For public read-only and anonymous resources, such as getting image info, looking up user comments, etc. all you need to do is send an authorization header with your client_id in your requests. This also works if you'd like to upload images anonymously (without the image being tied to an account), or if you'd like to create an anonymous album. This lets us know which application is accessing the API.
Authorization: Client-ID YOUR_CLIENT_ID
For accessing a user's account, please visit the OAuth2 section of the docs
Background:
I've got a C# website that requires OAuth Google log-in to access it's pages.
I also have an Android App where I want to access this Web API's pages (with HttpGet-requests and use the returned JSON). I did indeed make an OAuth Google log-in in my Android App, but I need to somehow use that to log-in on the Web API.
I used to have a POST on my C# website that required an AntiForgeryToken to log-in. Because these AntiForgeryTokens only exists very very short (Session-based and changes whenever another page is opened, even the same page in the same browser), I was forced to find a different way. (Because I tried to send a HttpPost in my Android App to log-in with an AntiForgeryToken that I received as a response in the last HttpGet-request, but this token just keeps changing non-stop and caused a no-match in my HttpPost response..)
Current:
Instead I made a new POST-method in my C# web project that doesn't require this [ValidateAntiForgeryToken]. I've also made a System.Web.Security.Role for the Web API pages, and in my API Controllers I've added [Authorize(Role = "web_api")].
When I tested this on my browser:
I removed all Cookies I have in my FireFox browser
I start a Private (Incognito) Window
And used the plug-in RESTClient to make the following POST:
URL: http://localhost:54408/Account/ApiLogin
Headers: Content-Type: application/x-www-form-urlencoded
Body: provider=Google&returnUrl=/Cart/Bestellen
It works with the following response:
When I try to go to this same POST on my Android app however (using the AsyncTask POST-method at the bottom of this page), I get an Error 400 (OAuth2 Error)!!1 in my response.
So, my question: How could I use this new POST that doesn't require any AntiForgeryToken to log-in with an OAuth Google Log-in on my Android App.
Or should I use a completely different approach to log-in with an OAuth Google account on my C# Web API, from within my Android app? (Could I send the OAuth user data from the separate Google log-in in the Android app itself through a different POST? Can I produce this GoogleAccountsLocal_session cookie that is somehow created after the POST-request myself in my Android App to use and put in the HttpClient? So I can get the same response as with the RESTClient POST-request above; if this is indeed the needed cookie to make it work?)
TL;DR: How to log-in to an OAuth2 Google secured C# Web API from within an Android App (so I can send the Web API HttpGet-requests and get JSON returned), without using AntiForgeryTokens whatsoever?
Thanks in advance for the responses.
Android POST class:
public class TaskPostAPI extends AsyncTask<String, Void, String>
{
#Override
protected String doInBackground(String... urls){
String response = "";
for(String url : urls){
InputStream content = null;
BufferedReader buffer = null;
try{
// Get the baseUrl from the given url
URL u = new URL(url);
String baseUrl = u.getProtocol() + "://" + u.getHost();
HttpPost post = new HttpPost(url);
// Add the default Content-type to the Header
post.addHeader("Content-type", "application/x-www-form-urlencoded");
// POST-request requires provider and returnUrl
List<NameValuePair> nvPairs = new ArrayList<NameValuePair>(2);
nvPairs.add(new BasicNameValuePair("provider", "Google"));
nvPairs.add(new BasicNameValuePair("returnUrl", baseUrl));
post.setEntity(new UrlEncodedFormEntity(nvPairs));
// Send the POST-request
HttpResponse execute = MainActivity.HttpClient.execute(post);
// Get the response of the POST-request
content = execute.getEntity().getContent();
buffer = new BufferedReader(new InputStreamReader(content));
String s = "";
while((s = buffer.readLine()) != null)
response += s;
}
catch(Exception ex){
ex.printStackTrace();
}
finally{
try{
if(content != null)
content.close();
if(buffer != null)
buffer.close();
}
catch(IOException ex){
ex.printStackTrace();
}
}
}
return response;
}
}