2

I am trying to execute a function provided by one app engine app that I have written (python) that uses Endpoints, in a second similar app engine app.

I currently have both app engine applications running on appspot using endpoints with oauth2. I have a working javascript client that consumes the endpoint, executes the functions with authorization and authentication. So I know the backend app engine servers are working and are a properly exposed endpoint. I can also browse the API using the api explorer and the discovery service.

Since this is a server to server link, I think that Service Accounts are what I want to use for the oauth2 authentication. So I created the Service account in the client app on the app engine console.

Here is the code that runs on the caller:

f = file('key2.pem', 'rb')
key = f.read()
f.close()

credentials = SignedJwtAssertionCredentials(
     'my-service-account-email-from-caller-app@developer.gserviceaccount.com',
      key,
      scope='https://my-app-id.appspot.com/_ah/api/my-api/v1')
http = credentials.authorize(httplib2.Http())
service = build("my-api", "v1", http=http)

When I run this code, I get an error: AccessTokenRefreshError: invalid_grant

I have tried many other things, adding a developerKey or a discoveryUrl parameter to the credentials, still invalid grant. I looked at other people who have seen this error and have tried messing with the clocks, although this is a server to server call so I don't think that is the problem. I have added the caller's service account email address to the permissions of the callee app.

I have not found a sample app or a post about using service accounts to call a custom Endpoints API, only to call Google APIs such as Youtube or Plus, most of which have a method for registering a calling app engine application.

Has anyone been able to call an endpoint api function on one app engine application with another app engine application using oauth2?

Thanks in advance, -mat

  • You're in uncharted territory. Endpoints as it currently exists is designed for client<->server communication where the client is operated by a user. – Dan Holevoet Feb 11 '14 at 22:38
  • Ever have any luck with this? I am trying to do the same thing now, but just from a python script running off my local computer. Having a real hard time getting my API request to authorize. – TedCap Sep 13 '14 at 03:27

1 Answers1

4

I've been able to get this working using a service account in an app-to-app scenario like you're describing. It's been a while since I touched this code, but quickly comparing your code to mine, the two differences I see are:

scope:

I'm passing in https://www.googleapis.com/auth/userinfo.email for the scope of the SignedJwtAssertionCredentials class. Given the error message you're seeing, this is likely the cause. I believe email scope is required as a bare minimum, based on this documentation.

My Endpoints services that I'm calling are also specifying allowed_client_ids as well to restrict access to a list of known clients, one of which is the Service account I provisioned to talk to these endpoints.

discoveryServiceUrl:

I'm passing in the discoverServiceUrl parameter to the build() function. Given your error message, this doesn't seem likely, but figured I'd mention it.

----EDIT----

Adding sample code per the request below.

For a google console project with id=1234567890, this is the client code I'm using to call my service endpoint:

from apiclient.discovery import build
from oauth2client.client import SignedJwtAssertionCredentials

service_account_email = '1234567890-aasgqg243rsaggw4@developer.gserviceaccount.com'
key_file_path = os.path.join(config.base_path, 'myservice.pem')

key_file = file(key_file_path, 'rb')
key = key_file.read()
key_file.close()

credential = SignedJwtAssertionCredentials(service_account_email, key, 'https://www.googleapis.com/auth/userinfo.email')
http = credential.authorize(httplib2.Http())

discoveryServiceUrl = 'http://mysite.appspot.com/_ah/api/discovery/v1/apis/{api}/{apiVersion}/rest'
myservice = build('myservice', 'v1', http=http, discoveryServiceUrl=discoveryServiceUrl)

And this is the service declaration with the allowed_client_ids parameter specifying my google api project (id: 1234567890)

@myservice_api.api_class(resource_name='myservice', path='myservice', allowed_client_ids=['1234567890.apps.googleusercontent.com', '1234567890-aasgqg243rsaggw4.apps.googleusercontent.com', endpoints.API_EXPLORER_CLIENT_ID])
class MyService(remote.Service):
    ...

Hope this helps.

Community
  • 1
  • 1
Brian Weller
  • 119
  • 5
  • Hey Brian, do you have any example code or a gist you can share? I've been beating my head against the wall trying to get this to work. – TedCap Sep 13 '14 at 03:31
  • 1
    Hey TedCap, I updated my answer with a code sample of both the client calling code and the service declaration...let me know if that works or if you need any clarification. – Brian Weller Sep 15 '14 at 04:17
  • Brian Weller, you are my hero of the week. Seriously, thank you so freaking much. That was all I needed. I finally got it working! That missing project id was the key to using the build() method with a custom api. – TedCap Sep 15 '14 at 21:22