Search code examples
androidgoogle-play-servicesgoogle-books

Google Books API Auth errors


I'm building an Android app and am trying to integrate the Google Books API into my app. Right now, logging in works. I get the consent screen and it contains a request for access to the Google Books data I have on my account. I'm now trying to fetch all bookshelves from my library using the following code:

public class GetUsernameTask extends AsyncTask<String, Void, Void> {
    Activity mActivity;
    String mScope;
    String mEmail;

    public GetUsernameTask(Activity activity, String name, String scope) {
        this.mActivity = activity;
        this.mScope = scope;
        this.mEmail = name;
    }

    /**
     * Executes the asynchronous job. This runs when you call execute()
     * on the AsyncTask instance.
     */
    @Override
    protected Void doInBackground(String... params) {
        try {
            String token = fetchToken();
            if (token != null) {
                Log.i("TAG", "Got a token: " + token);
                // Use the token to access the user's Google data.
                String api_key = params[0];
                URL object = new URL("https://www.googleapis.com/books/v1/mylibrary/bookshelves?key=" + api_key);
                HttpsURLConnection connection = (HttpsURLConnection) object.openConnection();
                connection.setRequestProperty("Authorization", token);
                connection.setRequestMethod("GET");
                connection.setDoInput(true);
                Log.i("TAG", connection.getResponseMessage());
                InputStream is = connection.getErrorStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                String line;
                while ((line = reader.readLine()) != null) {
                    Log.i("TAG", line);
                }
            }
        } catch (IOException e) {
            Log.e("TAG", "Got an exception", e);
        }
        return null;
    }

    /**
     * Gets an authentication token from Google and handles any
     * GoogleAuthException that may occur.
     */
    protected String fetchToken() throws IOException {
        try {
            return GoogleAuthUtil.getToken(mActivity, mEmail, mScope);
        } catch (UserRecoverableAuthException userRecoverableException) {
            // GooglePlayServices.apk is either old, disabled, or not present
            // so we need to show the user some UI in the activity to recover.

            Log.i("TAG", "Nu! more exceptions", userRecoverableException);
        } catch (GoogleAuthException fatalException) {
            // Some other type of unrecoverable exception has occurred.
            // Report and log the error as appropriate for your app.
            Log.i("TAG", "Nu! more exceptions", fatalException);
        }
        return null;
    }
}

This code comes from the Google documentation (https://developers.google.com/android/guides/http-auth).

As request, I append "?key=api_key", as specified by the documentation (https://developers.google.com/books/docs/v1/using?hl=en#WorkingMyBookshelves) and I've specified my token in the header, too. When printing the token, it looks like this: xxxx.YQJ8nJimB8eeQOIdVkkg2WfkobsBkARzc2TWJL4d_2ipIQuJ4U9uFTvNNhye7i0474TbsA

What I get back is surprising; it's a 401 HTTP error, namely the following:

 {
  "error": {
   "errors": [
    {
     "domain": "global",
     "reason": "authError",
     "message": "Invalid Credentials",
     "locationType": "header",
     "location": "Authorization"
    }
   ],
   "code": 401,
   "message": "Invalid Credentials"
  }
}

As API key, I tried to use the following:

I also found out I have two debug.keystore files on my harddrive, one in C:\Android\ and one in ~\.android. I simply decided to add both SHA-1 hashes to both Android keys (as well as the OAuth 2.0 client ID) in the hopes that it was somehow using the other keystore - to no avail.

What am I doing wrong?

I've also tried using the Google Play Services library. This gives different errors; but errors nonetheless.

I use this code (in the doInBackground method) to attempt at using said API:

try {
    Books service = new Books.Builder(AndroidHttp.newCompatibleTransport(), JacksonFactory.getDefaultInstance(), null).setGoogleClientRequestInitializer(new BooksRequestInitializer(api_key)).build();
    List<Bookshelf> shelves = service.mylibrary().bookshelves().list().execute().getItems();
}catch (Exception ex){
    Log.e("TAG", "Error while using Books", ex);
}

Using the Android keys, I get a 403 Forbidden HTTP response back, with 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.". However, I don't see any IP restrictions anywhere using Android API keys.

Using the server key, I get a 400 Bad Request, probably because the token is missing.

What am I doing wrong? What am I missing?


Solution

  • After asking some people close to me, I was noted to the way I am setting up the Authorization header. According to the Wiki (https://en.wikipedia.org/wiki/Basic_access_authentication#Client_side) the token is preceded by a "Type". In the case of Google OAuth2, this type is Bearer.

    Therefore, changing

    connection.setRequestProperty("Authorization", token);
    

    into

    connection.setRequestProperty("Authorization", "Bearer " + token);
    

    did the trick. EDIT: Apparently, this was specified here: https://developers.google.com/books/docs/v1/using?hl=en#query-params - all the way at the bottom of the page under a header that I don't feel is appropriate for something so essential.

    As for the Google API, I now realized I had not specified the token anywhere. After searching a bit, I found that you can set the Oauth token like this:

    List<Bookshelf> shelves = service.mylibrary().bookshelves().list().setOauthToken(token).execute().getItems();
    

    This gave me the wanted result. Please note: This was done using the Server key, not the Android keys. The Android keys were still giving me the 403 ipRefererBlocked errors.