Is there a way to secure Google cloud endpoints proto datastore?

1k views Asked by At

my setup:

  1. Python, google app engine using endpoints_proto_datastore
  2. iOS, endpoints Obj-C client library generator

Background

I've setup a test Google cloud endpoints api and had it running pretty quickly. It works great, using the test app in the iOS simulator, and using Google's APIs Explorer. The API is currently open to all with no authentication.

I'd like to: setup an API-key or system-credential that can be used by the app to ensure it alone can access the api - with all others being refused.

The method in the Google Endpoints Auth docs (1) is to create an OAuth 2.0 client ID using the Google Developers Console (2). So I have created an ID for an Installed Application of type: iOS. So far so good.

In the app the GTLService Object looks like this...

-(GTLServiceDemogaeapi *)myApiService {
    GTMOAuth2Authentication *auth = [[GTMOAuth2Authentication alloc] init];
    auth.clientID = @"10???????????????????ie.apps.googleusercontent.com";
    auth.clientSecret = @"z????????????3";
    auth.scope = @"https://www.googleapis.com/auth/userinfo.email";

    static GTLServiceDemogaeapi *service = nil;
    if (!service) {
        service = [[GTLServiceDemogaeapi alloc] init];
        service.authorizer = auth;
        service.retryEnabled = YES;
        [GTMHTTPFetcher setLoggingEnabled:YES];
    }
    return service;
 }

On GAE I've specified the (allowed_client_ids and added a user check to the methods...

@endpoints.api(name='demogaeapi', version='v1',
               allowed_client_ids=['10?????????????ie.apps.googleusercontent.com',
               endpoints.API_EXPLORER_CLIENT_ID],
               scopes=[endpoints.EMAIL_SCOPE],
               description='My Demo API')
class MyApi(remote.Service):

    @TestModel.method(path='testmodel/{id}', name='testmodel.insert', http_method='POST')
    def testModelInsert(self, test_model):

        current_user = endpoints.get_current_user()
        if current_user is None:
            raise endpoints.UnauthorizedException('Invalid token.')

The issue

current_user is always None so the method will always raise an exception. It appears that this is a known problem Issue 8848: Google Cloud Enpoints get_current_user API doesn't fill user_id (3) with no prospect of a fix soon.

Options?

  1. Wait until google fix Issue 8848. Can't really, we have a product to release!

EDIT: 15 May 2015 - Google made Issue 8848 status WontFix.

  1. I saw that it may be possible to use an API Key but while I have been able to create one - I have not found a way to enable it on the backend. I also note this method has a big hole where Google's APIs Explorer can defeat it see SO question (4).

  2. Does the @endpoints.api Argument: auth_level, described here (5), provide an answer? I tried using:

    @endpoints.api(name='demogaeapi', version='v1',
           auth_level=endpoints.AUTH_LEVEL.REQUIRED,
           scopes=[endpoints.EMAIL_SCOPE],
           description='My Demo API')
    

    But was able to use the api from the client app without using credentials. So it clearly did not add any authentication.

  3. Add a hiddenProperty to the client query holding a shared secret key. As described by bossylobster here (6) and Carlos here (7). I tried this but cannot see how to get at the raw request object (subject of another question How to get at the request object when using endpoints_proto_datastore.ndb?).

    @TestModel.method(path='testmodel/{id}', name='testmodel.insert', http_method='POST')
    def testModelInsert(self, test_model):
    
        mykey,keytype = request.get_unrecognized_field_info('hiddenProperty')
        if mykey != 'my_supersecret_key':
            raise endpoints.UnauthorizedException('No, you dont!')
    

EDIT: Another option has surfaced:

  1. Create a Service Account using the Google Developers Console (2). Use this account to gain access to the API without the need for user consent (via UI). However, Google appear to limit the number of apps that can be added this way to 15 or 20. See Google OAuth2 doc (8). We will likely exceed the limit.

The Question

Does anyone know how I can get any of these options working? Or should I be approaching this in a different way?

As you can see I'm in need of guidance, help, ideas...


As I need 10 reputation to post more than 2 links: here are the links I had to extract and add as references. Sort of ruined the flow of the question really.

  1. cloud.google.com/appengine/docs/python/endpoints/auth
  2. console.developers.google.com/
  3. code.google.com/p/googleappengine/issues/detail?id=8848
  4. stackoverflow.com/a/26133926/4102061
  5. cloud.google.com/appengine/docs/python/endpoints/create_api
  6. stackoverflow.com/a/16803274/4102061
  7. stackoverflow.com/a/26133926/4102061
  8. developers.google.com/accounts/docs/OAuth2
1

There are 1 answers

0
Brian White On

An excellent question and a real problem. I solved it by having my own "user" database table. The Long key (I use Java) is auto-generated on write and that is the value I use to uniquely identify the user from then on. Also written to that table are the Google ID (provided if being called through a web page), email address, and iOS id (when using the iOS app while not signed-in to a Google account).

When my AppEngine endpoint gets called, I use information that is present in the User parameter (just the authenticated email address for signed-in clients) or a separate, optional iosid parameter in order to fetch my internal userid number from said table and use that from then on to uniquely identify my user.

I use the same table to store other user-specific information, too, so it really isn't any different than trying to use current_user as a primary key other than I had to make three fields (googleid, email, iosid) indexed for lookup.