How do I restrict Google App Engine Endpoints API access to only my Android applications?-ThrowExceptions

Exception or error:

I am an Android developer building my first Google App Engine (java) back-end for my apps. I don’t want anybody else to access this API other than my app. (I plan to use App engine for verifying InApp purchases in my Android app). My data is not relevant to users so,
I don’t want users to be able to access my API even if they are logged in with their Google accounts (on web or Android devices).

I followed the steps mentioned in – “Specifying authorized clients in the API backend”
(https://developers.google.com/appengine/docs/java/endpoints/auth)
like generating client IDs and add them in @Api (clientIds and audiences)
except “Add a User parameter” – since I don’t need user authentication.

Then I deployed App engine and I am still able to access the API through API explorer (https://your_app_id.appspot.com/_ah/api/explorer)
(I haven’t added API_EXPLORER client ID)

I tested with the APK that was built with the endpoint libs before adding client IDs and can still access the API.

  • Is adding a “User parameter” to all endpoint APIs a must? to achieve my purpose (restrict API to only my Android apps).

  • Can I pass null as userAccount name from Android client and ignore user parameter value on server (since it will be null)? Will this ensure that the API is accessible only from my android apps (since the client ID is generated for my package name and SHA1 of the APK?)

  • Should I use something like a service account for this purpose?

The documentation says for Android, both Android and Web client IDs must be added and audience must be the same as web client ID. Does this open access to any other web client? can I skip mentioning web client ID and still achieve my purpose?

Appreciate your time and help.

…… updating with my further investigation …

I did the following:

  • Added User parameter to APIs on backend – but did not check for null value. API can still be accessed without passing any credentials (from Android debug APK and API explorer)

  • Then, I tried

    mCredential = GoogleAccountCredential.usingAudience(this, “server:client_id:” + WEB_CLIENT_ID);
    mCredential.setSelectedAccountName(null);

and passed this credential to API builder (as suggested in some other posts)
Caused FATAL EXCEPTION. So, we can’t pass null account name.

  • I could call the API using API explorer without OAuth. But when I enabled OAuth, it gave error saying this client ID is not allowed! ( I haven’t yet added com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID in client_ids{})

  • Then I added code to throw OAuthRequestException on the backend if the user is null. This resulted in API explorer getting errors without OAuth. It works with OAuth enabled after adding API_EXPLORER_CLIENT_ID to client_ids)

  • Added code to pass valid user account name(email) from my Android app. Then, I am able to access API only with my release APK. Even the debug APK gets exceptions! – which is what I expected..So, I assume no other Android apps will be able to access this API.

So, not checking for null user on back-end API is a bad idea (as suggested in other posts). It is as good as not mentioning any client_ids and not having User param.

Only question I have at this moment is: If some one can figure out the WEB_CLIENT_ID from the APK, will they be able to use it to build a web client to access my API (I haven’t mentioned client secret anywhere in the code. So I am thinking this is not possible).


I did search Google groups and Stackoverflow, but still it is not clear.

How to solve:

I had a similar issue, not between Android and App Engine, but between a separate server and App Engine. The way I handled it was to add a signature hash field as a parameter to each API call. If the request had an improper signature, it would be denied.

For example, suppose your API end-point is example.com/api/do_thing?param1=foo. I would hash the entire url, along with a secret key, and then append the result of the hash to the request: example.com/api/do_thing?param1=foo&hash=[some long hex value].

Then, on the server side, I would first remove the hash from the url request, then run the hash on everything that was remaining. Finally, you check whether the calculated hash matches the one that was sent with the request and if they don’t, you can deny the request.

It is very important however that your secret key remain secret. You have to be careful with this on Android because someone could attempt to decompile your APK.

###

Facing the same problem than you ! Authenticate Android End point without Google User Account is just impossible !

So here is my way to resolv this problem, without any user interaction (Maybe not the right but that works, and you’ve got strong authentication (SHA1 + Google Account)):

HERE IS MY ANDROID CODE

Get and Build Valid Credential

  //Get all accounts from my Android Phone
         String validGoogleAccount = null;
         Pattern emailPattern = Patterns.EMAIL_ADDRESS; // API level 8+
         Account[] accounts = AccountManager.get(context).getAccounts();
         for (Account account : accounts) {
             if (emailPattern.matcher(account.name).matches()) {
                 //Just store mail if countain gmail.com
                 if (account.name.toString().contains("gmail.com")&&account.type.toString().contains("com.google")){
                     validGoogleAccount=account.name.toString();
                 }

             }
         }

        //Build Credential with valid google account
        GoogleAccountCredential credential = GoogleAccountCredential.usingAudience(this,"server:client_id:301991144702-5qkqclsogd0b4fnkhrja7hppshrvp4kh.apps.googleusercontent.com");
        credential.setSelectedAccountName(validGoogleAccount);

Use this credential for secure calls

Campagneendpoint.Builder endpointBuilder = new Campagneendpoint.Builder(AndroidHttp.newCompatibleTransport(), new JacksonFactory(), credential);

HERE IS MY API BACKEND CODE:
API Annotation

@Api(
        scopes=CONSTANTES.EMAIL_SCOPE,
        clientIds = {CONSTANTES.ANDROID_CLIENT_ID, 
                     CONSTANTES.WEB_CLIENT_ID,
                     com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID},
        audiences = {CONSTANTES.ANDROID_AUDIENCE},      
        name = "campagneendpoint",
        version = "v1"
     )

Method code:

public Collection<Campagne> getCampagnes(@Named("NumPortable")String NumPortable, User user) throws  UnauthorizedException {
        if (user == null) throw new UnauthorizedException("User is Not Valid");

      return CampagneCRUD.getInstance().findCampagne(NumPortable);
     }

For the moment, it only works on Android (I don’t know how we gonna do on IOS..)..

Hope It will help you !

###

Google provides ways to do this for Android, web and iOS
The steps involves:

  1. Specifying a client Id for apps you want to allow to make requests to your API
  2. Adding a User parameter to all exposed methods to be protected by authorization.
  3. Generating the client library again for any Android clients
  4. Redeploying your backend API.
  5. Updating the regenerated jar file to your Android project for your Android client.

These steps are laid out in clear detail on Google’s Using Auth with Endpoints and also on this blog

###

Facing the same problem, here are the result of my research :

  • Added Android cliend id with SHA1 fingerprint in Google console
  • Use of it in the API annotation

BUT :

  • If i dont add user parameter to methods : the check about android app client id does not work

  • If I add the USER parameter but do not ask the user to choose its google account to create the credential … also it does not work …

Conclusion : It seems to be mandatory to connect a user account for the check about the app client id to be executed … I really do not understand why because no link exist between the 2 processes

###

Access this site

Choose your project, go to credentials section

Create a new api key

Create a new android key

Click on “Edit allowed android applications” and enter your SHA1 key; your android package name

Let me know if this solves the issues.

Leave a Reply

Your email address will not be published. Required fields are marked *