Search code examples
pythongoogle-app-enginegoogle-cloud-endpoints

Authenticating to Endpoints (Std env) with default GAE service account is giving "401 Method does not allow callers without established identity"


I'm trying to create a Google Cloud Endpoints in an AppEngine Standard environment service with 2 methods of authentication: apiKey and default GAE service account.

  • apiKey authentication is for external systems to be able to query the API
  • default GAE authentication is for other services (formerly known as "modules") within the same AppEngine app (XXXX) to connect to the endpoint (e.g. service1-dot-XXXX.appspot.com to make requests to an endpoint in api-dot-XXXX.appspot.com)

The apiKey authentication works just fine, but the "service_to_service_gae" authentication gives:

401 Method does not allow callers without established identity. Please use an API key or other form of API consumer identity to call this API.

I am decorating the endpoint with:

@endpoints.api(
    name='widgets',
    version='v1',
    base_path='/api/',
    api_key_required=True,
    allowed_client_ids=['XXXX@appspot.gserviceaccount.com'])

class WidgetsApi(remote.Service):
... 

And calling the API with this code based on the sample client from github

SERVICE_ACCOUNT_EMAIL = 'XXXX@appspot.gserviceaccount.com'
def generate_jwt():
  """Generates a signed JSON Web Token using the Google App Engine default
  service account."""
  now = int(time.time())

  header_json = json.dumps({
      "typ": "JWT",
      "alg": "RS256"})

  payload_json = json.dumps({
      "iat": now,
      # expires after one hour.
      "exp": now + 3600,
      # iss is the service account email.
      "iss": SERVICE_ACCOUNT_EMAIL,
      "sub": SERVICE_ACCOUNT_EMAIL,
      "email": SERVICE_ACCOUNT_EMAIL,
      "aud": 'https://api-dot-XXXX.appspot.com',
  })

  header_and_payload = '{}.{}'.format(
      base64.urlsafe_b64encode(header_json),
      base64.urlsafe_b64encode(payload_json))
  (key_name, signature) = app_identity.sign_blob(header_and_payload)
  signed_jwt = '{}.{}'.format(
      header_and_payload,
      base64.urlsafe_b64encode(signature))
  return signed_jwt

def make_request(signed_jwt):
  """Makes a request to the auth info endpoint for Google JWTs."""    
  headers = {'Authorization': 'Bearer {}'.format(signed_jwt)}
  conn = httplib.HTTPSConnection('api-dot-XXXX.appspot.com')
  url = '/api/widgets/v1/list'
  conn.request("POST", url, urllib.urlencode({'search': ''}), headers)
  res = conn.getresponse()
  conn.close()
  return res.read()

Am I forgetting something in the endpoint decorator or any other configuration? Or maybe the endpoint decorator accepts only one method of authentication? I would thing making a call from service to service within the same GAE std instance would be straight forward. The sample client is kind of confusing (at least for me) e.g. make_request makes a request ('/auth/info/googlejwt') to get the jwt token, but when do you call the actual endpoint?

Thanks in advance, happy New Year!!!


Solution

  • When api_key_required is true, you have to provide an API key in the request in addition to any JWTs.