22

The API is a backend to a mobile app. I don't need user authentication. I simply need a way to secure access to this API. Currently, my backend is exposed.

The documentation seems to only talk about user authentication and authorization, which is not what I need here. I just need to ensure only my mobile app can talk to this backend and no one else.

mixmasteralan
  • 489
  • 4
  • 11
  • Actually you are talking about authentication and authorization. How would you propose securing an endpoint for only your application without some form of authentication, (either application or enduser based). Endpoints are just web services that adhere to a specific public api. – Tim Hoffman Jun 03 '13 at 05:58
  • One example way of doing this is Service Accounts: https://developers.google.com/accounts/docs/OAuth2ServiceAccount However, this is only for Google API's. The documentation is only for the client. What I need is documentation on how to provide Service Account functionality on the server side. – mixmasteralan Jun 03 '13 at 06:10
  • Another way is using your own hash value between your app and endpoint http://stackoverflow.com/questions/2447470/recaptcha-on-iphone-app-using-sdk – Ton Dec 03 '13 at 19:00

3 Answers3

5

Yes, you can do that: use authentication to secure your endpoints without doing user authentication.

I have found that this way of doing it is not well documented, and I haven't actually done it myself, but I intend to so I paid attention when I saw it being discussed on some of the IO13 videos (I think that's where I saw it):

Here's my understanding of what's involved:

  • Create a Google API project (though this doesn't really involve their API's, other than authentication itself).
  • Create OATH client ID's that are tied to your app via its package name and the SHA1 fingerprint of the certificate that you will sign the app with.

You will add these client ID's to the list of acceptable ID's for your endpoints. You will add the User parameter to your endpoints, but it will be null since no user is specified.

@ApiMethod(
   name = "sendInfo",
   clientIds = { Config.WEB_CLIENT_ID, Config.MY_APP_CLIENT_ID, Config.MY_DEBUG_CLIENT_ID },
   audiences = { Config.WEB_CLIENT_ID } 
   // Yes, you specify a 'web' ID even if this isn't a Web client.
)
public void sendInfo(User user, Info greeting) {

There is some decent documentation about the above, here: https://developers.google.com/appengine/docs/java/endpoints/auth

Your client app will specify these client ID's when formulating the endpoint service call. All the OATH details will get taken care of behind the scenes on your client device such that your client ID's are translated into authentication tokens.

HttpTransport transport = AndroidHttp.newCompatibleTransport();
JsonFactory jsonFactory = new JacksonFactory();
GoogleAccountCredential credential = GoogleAccountCredential.usingAudience( ctx, Config.WEB_CLIENT_ID );
//credential.setSelectedAccountName( user );  // not specify a user
Myendpoint.Builder builder = new Myendpoint.Builder( transport, jsonFactory, credential );  

This client code is just my best guess - sorry. If anyone else has a reference for exactly what the client code should look like then I too would be interested.

Tom
  • 17,103
  • 8
  • 67
  • 75
  • 1
    You can't "not specify a user" in credential.setSelectedAccountName(); - it takes a required string argument... – Micro Aug 17 '15 at 18:41
  • 1
    Tom: Did this ever work for you? @MicroR: Are you saying it will take empty string? – sam Mar 03 '17 at 10:03
  • @sam, sorry but I don't remember - i'm no longer involved in that area. – Tom Mar 03 '17 at 16:55
2

I'm sorry to say that Google doesn't provide a solution for your problem (which is my problem too). You can use their API key mechanism (see https://developers.google.com/console/help/new/#usingkeys), but there is a huge hole in this strategy courtesy of Google's own API explorer (see https://developers.google.com/apis-explorer/#p/), which is a great development tool to test API's, but exposes all Cloud Endpoint API's, not just Google's services API's. This means anyone with the name of your project can browse and call your API at their leisure since the API explorer circumvents the API key security. I found a workaround (based on bossylobster's great response to this post: Simple Access API (Developer Key) with Google Cloud Endpoint (Python) ), which is to pass a request field that is not part of the message request definition in your client API, and then read it in your API server. If you don't find the undocumented field, you raise an unauthorized exception. This will plug the hole created by the API explorer. In iOS (which I'm using for my app), you add a property to each request class (the ones created by Google's API generator tool) like so:

@property (copy) NSString *hiddenProperty;

and set its value to a key that you choose. In your server code (python in my case) you check for its existence and barf if you don't see it or its not set to the value that your server and client will agree on:

mykey,keytype = request.get_unrecognized_field_info('hiddenProperty')
        if mykey != 'my_supersecret_key':
            raise endpoints.UnauthorizedException('No, you dont!')

Hope this puts you on the right track

Community
  • 1
  • 1
Carlos Guzman
  • 429
  • 6
  • 12
  • But if someone decompiles your Android APK couldn't they just see the plain text request field and then create another Android app and play with your API? In javascript it would be even easier since you can just view the source. – Micro Aug 17 '15 at 20:33
  • I'm not familiar with APK's or how easy it is to decompile. In my case, an iOS app it's fairly difficult to try to get at the code that does this. – Carlos Guzman Aug 19 '15 at 05:30
  • @MicroR In client side can be created quasi complex method that finally generating some, for example, simple long value that sent to server. After being obfuscated, even decompiled it will looks like non-sense set of arithmetical actions. And on server side check if received value after predefined set of another actions is divided on, let say 17. – Yuriy N. Mar 14 '16 at 21:52
  • @MicroR with pleasure: function c(){var a,b=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],f=0,d=0,g=0,e=0;b[0]=6;b[1]=0;b[2]=1;b[3]=1;a=4;for(e=16;a – Yuriy N. Mar 15 '16 at 09:19
2

The documentation is only for the client. What I need is documentation on how to provide Service Account functionality on the server side.

This could mean a couple of different things, but I'd like to address what I think the question is asking. If you only want your service account to access your service, then you can just add the service account's clientId to your @Api/@ApiMethod annotations, build a GoogleCredential, and invoke your service as you normally would. Specifically...

In the google developer's console, create a new service account. This will create a .p12 file which is automatically downloaded. This is used by the client in the documentation you linked to. If you can't keep the .p12 secure, then this isn't much more secure than a password. I'm guessing that's why this isn't explicitly laid out in the Cloud Endpoints documentation.

You add the CLIENT ID displayed in the google developer's console to the clientIds in your @Api or @ApiMethod annotation

import com.google.appengine.api.users.User

@ApiMethod(name = "doIt", scopes = { Constants.EMAIL_SCOPE }, 
     clientIds = { "12345678901-12acg1ez8lf51spfl06lznd1dsasdfj.apps.googleusercontent.com" })
public void doIt(User user){ //by convention, add User parameter to existing params
    // if no client id is passed or the oauth2 token doesn't 
    // match your clientId then user will be null and the dev server 
    // will print a warning message like this:
    // WARNING: getCurrentUser: clientId 1234654321.apps.googleusercontent.com not allowed
    //..
}

You build a client the same way you would with the unsecured version, the only difference being you create a GoogleCredential object to pass to your service's MyService.Builder.

HttpTransport httpTransport = new NetHttpTransport(); // or build AndroidHttpClient on Android however you wish
JsonFactory jsonFactory = new JacksonFactory();

// assuming you put the .p12 for your service acccount 
// (from the developer's console) on the classpath; 
// when you deploy you'll have to figure out where you are really
// going to put this and load it in the appropriate manner 
URL url = getClass().class.getResource("/YOURAPP-b12345677654.p12");
File p12file = new File(url.toURI());

GoogleCredential.Builder credentialBuilder = new GoogleCredential.Builder();
credentialBuilder.setTransport(httpTransport);
credentialBuilder.setJsonFactory(jsonFactory);
//NOTE: use service account EMAIL (not client id)
credentialBuilder.setServiceAccountId("12345678901-12acg1ez8lf51spfl06lznd1dsasdfj@developer.gserviceaccount.com");    credentialBuilder.setServiceAccountScopes(Collections.singleton("https://www.googleapis.com/auth/userinfo.email"));
credentialBuilder.setServiceAccountPrivateKeyFromP12File(p12file);
GoogleCredential credential = credentialBuilder.build();

Now invoke your generated client the same way you would the unsecured version, except the builder takes our google credential from above as the last argument

MyService.Builder builder = new MyService.Builder(httpTransport, jsonFactory, credential);
builder.setApplicationName("APP NAME");
builder.setRootUrl("http://localhost:8080/_ah/api");

final MyService service = builder.build();
// invoke service same as unsecured version
Tom
  • 101
  • 2
  • 3
  • Tom, I am trying this on dev, but the user that is passed is always null,even though the authorization is done using my email id which is used in the GAE project.The client id that I provided in the @Apimethod should not be wrong since i am successfully using it to access google cloud storage. Have you got this working ? – maya Jun 02 '15 at 14:24
  • This doesn't work. I tried for web client. But i am able to make call from any client. Perhaps it should block clients other than my client id. But it does not. – Bharathi Mar 31 '16 at 09:47