Search code examples
authenticationgoogle-cloud-platformgoogle-cloud-endpointsrestful-authentication

How to make an authenticated call to Google Cloud Endpoint?


I've set up a simple, standard environment Google App Engine project which uses Cloud Endpoints by going through the steps in the tutorial here:

https://cloud.google.com/endpoints/docs/frameworks/python/get-started-frameworks-python

This works great - I can make a curl call to the echo endpoint and get the expected result.

However, I can't successfully call the authenticated endpoint. I'm following the steps here: https://cloud.google.com/endpoints/docs/frameworks/python/javascript-client and, while I can successfully sign in, when I send my sample authenticated request I get a 401 Unauthorized HTTP response.

From the log on the server I see :

Client ID is not allowed: <my client id>.apps.googleusercontent.com (/base/data/home/apps/m~bfg-data-analytics/20190106t144214.415219868228932029/lib/endpoints/users_id_token.py:508)

So far I've checked:

  • The web app is using the correct version of the cloud endpoints config.
  • The client ID in the endpoint config (x-google-audiences) matches the client ID that the javascript web app is posting.

Any ideas on how to fix this?


Solution

  • Using the example code to set up the end point in: https://cloud.google.com/endpoints/docs/frameworks/python/create_api and https://cloud.google.com/endpoints/docs/frameworks/python/service-account-authentication

    And modifying the python code for generating a token from : https://github.com/GoogleCloudPlatform/python-docs-samples/tree/master/endpoints/getting-started/clients/service_to_service_google_id_token

    I've got it working.

    Here's the server endpoint code:

    import endpoints
    from endpoints import message_types
    from endpoints import messages
    from endpoints import remote
    
    class EchoRequest(messages.Message):
        message = messages.StringField(1)
    
    class EchoResponse(messages.Message):
        """A proto Message that contains a simple string field."""
        message = messages.StringField(1)
    
    
    ECHO_RESOURCE = endpoints.ResourceContainer(
        EchoRequest,
        n=messages.IntegerField(2, default=1))
    
    @endpoints.api(
        name='echo',
        version='v1',
        issuers={'serviceAccount': endpoints.Issuer(
        '[email protected]',
        'https://www.googleapis.com/robot/v1/metadata/x509/[email protected]')},
        audiences={'serviceAccount': ['[email protected]']})
    
    class EchoApi(remote.Service):
        # Authenticated POST API
        # curl -H "Authorization: Bearer $token --request POST --header "Content-Type: applicationjson" --data '{"message":"echo"}' https://[email protected]/_ah/api/echo/v1/echo?n=5
        @endpoints.method(
            # This method takes a ResourceContainer defined above.
            ECHO_RESOURCE,
            # This method returns an Echo message.
            EchoResponse,
            path='echo',
            http_method='POST',
            name='echo')
        def echo(self, request):
            print "getting current user"
            user = endpoints.get_current_user()
            print user
            # if user == none return 401 unauthorized
            if not user:
                raise endpoints.UnauthorizedException
    
            # Create an output message including the user's email
            output_message = ' '.join([request.message] * request.n) + ' ' + user.email()
            return EchoResponse(message=output_message)
    
    api = endpoints.api_server([EchoApi])
    

    And the code to generate a valid token

        import base64
        import json
        import time
        import google
    
        import google.auth
        from google.auth import jwt
    
        def generate_token(audience, json_keyfile, client_id, service_account_email):
            signer = google.auth.crypt.RSASigner.from_service_account_file(json_keyfile)
    
            now = int(time.time())
            expires = now + 3600  # One hour in seconds
    
            payload = {
                'iat': now,
                'exp': expires,
                'aud' : audience,
                'iss': service_account_email,
                'sub': client_id,
                'email' : service_account_email
            }
    
            jwt = google.auth.jwt.encode(signer, payload)
    
            return jwt
    
    token = generate_token(
        audience="[email protected]",              # must match x-google-audiences
        json_keyfile="./key-file-for-service-account.json",
        client_id="xxxxxxxxxxxxxxxxxxxxx",                              # client_id from key file
        service_account_email="[email protected]")
    
    print token
    

    Make an authenticated call with curl

    export token=`python main.py` 
    curl -H "Authorization: Bearer $token" --request POST --header "Content-Type: application/json" --data '{"message":"secure"}' https://MY-PROJECT.appspot.com/_ah/api/echo/v1/echo?n=5