21
votes

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.

3
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
One example way of doing this is Service Accounts: 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
Another way is using your own hash value between your app and endpoint stackoverflow.com/questions/2447470/…Ton

3 Answers

4
votes

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.

2
votes

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

2
votes

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