Service-to-Service Authentication

This page shows how to authenticate service-to-service calls in Google Cloud Platform using Google Cloud Endpoints. At a high level, you'll need to:

  1. Make changes to your OpenAPI configuration file to support authentication tokens.
  2. Add code to the calling service that creates the expected tokens.
  3. Redeploy your OpenAPI configuration file.
  4. Redeploy the API backend code.

In order for any service to make authenticated calls into an Endpoints API, the calling service must have a service account and it must send an auth token in the call. The caller must use a token in the form of a JSON Web Token (JWT) signed by Google (a Google ID token) or a custom JWT that is signed only by the service account of the caller.

Before you begin

  • To add auth support to your API, you must have a configured and deployable API.
  • You must have a project for your API and/or calling service. See one of the Tutorials for details on creating a project.
  • To enable a calling service to make authenticated calls to an API, obtain and configure a service account:

    1. For App Engine projects, you can optionally just use the default App Engine service account automatically created and configured for the project. If you choose this, then ignore the steps that create a non-default service account.

    2. Whether you are using a default or non-default service account, set the role Service Account Actor on the App Engine default account:

      Set the role

      1. Select the App Engine default service account.
      2. In the Roles column, in the Roles pulldown menu, click Roles > Project > Service Account Actor.
    3. If you want to create a new non-default service account to use, create the service account for the caller in the console IAM admin page
      Create a service account

      1. Click Create Service Account.
      2. Supply the service account name you want to use.
      3. Click Roles > Project > Service Account Actor.
      4. Click Create.
    4. If you want to use a key file:

      1. Select the service account you are creating the private key for.
      2. Select JSON as the key type.
      3. Click Create to download the .json key file to your local machine: note the location and make sure it is stored securely: you'll need to use it later.

Configuring your API to support authentication

You must have a security requirement object and a security definitions object in your OpenAPI configuration file to support authentication of a calling service.

The following tabs show the configuration file entries needed to support each type of authentication token.

Google ID JWT

To configure your API:

  1. Allow access to the Google ID token server accounts.google.com or https://accounts.google.com as an auth issuer in the configuration file for your API:

    securityDefinitions:
      google_id_token:
        authorizationUrl: ""
        flow: "implicit"
        type: "oauth2"
        x-google-issuer: "https://accounts.google.com"
    

    Notice that you don't need to provide the public key URI for Google ID token issuer because the Extensible Service Proxy automatically obtains it.

  2. Add a security section at either the top level of the file (not indented or nested) to apply to the entire API, or at the method level to apply to a specific method.

    security:
      - google_id_token: []
    

    Notice that if you use security sections at both the API level and at the method level, the method-level settings override the API-level settings.

Because this configuration uses Google ID JWT for authentication, after you deploy this configuration, all of your existing service accounts as well as any service accounts that you subsequently create will be able to authenticate.

Service Account-JWT

To configure your API:

  1. Add the service account as an auth issuer in the configuration file for your API:

    securityDefinitions:
      [ISSUER_NAME]:
        authorizationUrl: ""
        flow: "implicit"
        type: "oauth2"
        x-google-issuer: "[YOUR-SERVICE-ACCOUNT-EMAIL]"
        x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/[YOUR-SERVICE-ACCOUNT-EMAIL]"
    

    Replace [YOUR-SERVICE-ACCOUNT-EMAIL] with your service account email. Do not include the square brackets. You must add the service accounts of all the clients you are granting access to as an issuer. For example, if you have two service accounts, you would add the following in the securityDefinitions section:

    securityDefinitions:
      google_jwt_client-1:
        authorizationUrl: ""
        flow: "implicit"
        type: "oauth2"
        x-google-issuer: "client-1@example-project-176120.iam.gserviceaccount.com"
        x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/client-1@example-project-176120.iam.gserviceaccount.com"
      google_jwt_client-2:
        authorizationUrl: ""
        flow: "implicit"
        type: "oauth2"
        x-google-issuer: "client-2@example-project-176120.iam.gserviceaccount.com"
        x-google-jwks_uri: "https://www.googleapis.com/robot/v1/metadata/x509/client-2@example-project-176120.iam.gserviceaccount.com"
    
  2. Add a security section at either the top level of the file (not indented or nested) to apply to the entire API, or at the method level to apply to a specific method.

    security:
      - [ISSUER_NAME]: []
    

    Replace [ISSUER_NAME] with the name you want to use for this security definition.

    Notice that if you use security sections at both the API level and at the method level, the method-level settings override the API-level settings.

Enabling a calling service to make authenticated calls

The following table shows the options you have in creating tokens for making authenticated calls:

Token Type Service account options Where to send the JWT
Google ID token
  • Sign with App Engine default service account
  • Sign with any service account
  • Sign with key file from any service account
Send the JWT to Google Token endpoints to request Google ID token, then send the Google ID token in API requests.
Service Account-signed JWT
  • Sign with App Engine service account
  • Sign with any service account
  • Sign with key file for any service account
Send the service account-signed JWT in API requests.

The JWT used for service-to-service authentication should have the target service name as the audience, in the form of https://SERVICE_NAME.

Making an authenticated call to an Endpoints API

When you send a request using a JWT or Google ID token, for security reasons, we recommend that you put the authentication token in the Authorization:Bearer header. For example:

curl -H "Authorization: Bearer ${TOKEN}" "${ENDPOINTS_HOST}/echo"

where ENDPOINTS_HOST and TOKEN are environment variables containing your API host name and authentication token, respectively. See the last step of Using a JWT signed only by the service account and Using a Google ID token for sample code that sends a request using the Authorization:Bearer header.

If you cannot use the header when sending the request, you can put the authentication token in a query parameter called access_token. For example:

curl "${ENDPOINTS_HOST}/echo?access_token=${TOKEN}"

Using a JWT signed only by the service account

The following tabs shows code that creates a JWT, signs it, and uses the signed JWT to make an API request.

The tabs show how to do this for:

  • A default service account
  • A non default service account
  • A key file

default service account

To use a JWT signed with a default service account:

  1. Create a JWT and sign it with the default service account:

    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 Google App Engine default service account email.
            'iss': DEFAULT_SERVICE_ACCOUNT,
            'sub': DEFAULT_SERVICE_ACCOUNT,
            # aud must match 'audience' in the security configuration in your
            # OpenAPI spec.It can be any string.
            'aud': 'echo.endpoints.sample.google.com',
            "email": DEFAULT_SERVICE_ACCOUNT
        })
    
        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

    Replace aud with https://SERVICE_NAME where SERVICE_NAME is the value of the host entry in the API configuration file.

  2. Send the JWT in a request:

    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(HOST)
        conn.request("GET", '/auth/info/googlejwt', None, headers)
        res = conn.getresponse()
        conn.close()
        return res.read()

non-default

To use a JWT signed with a non-default service account:

  1. Create a JWT and sign it with the service account:

    def generate_jwt():
        """Generates a signed JSON Web Token using a service account."""
        credentials = google.auth.app_engine.Credentials(
            scopes=['https://www.googleapis.com/auth/iam'])
        service = googleapiclient.discovery.build(
            serviceName='iam', version='v1', credentials=credentials)
    
        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,
            # aud must match 'audience' in the security configuration in your
            # swagger spec.It can be any string.
            'aud': 'echo.endpoints.sample.google.com',
            "email": SERVICE_ACCOUNT_EMAIL
        })
    
        header_and_payload = '{}.{}'.format(
            base64.urlsafe_b64encode(header_json),
            base64.urlsafe_b64encode(payload_json))
        slist = service.projects().serviceAccounts().signBlob(
            name=SERVICE_ACCOUNT,
            body={'bytesToSign': base64.b64encode(header_and_payload)})
        res = slist.execute()
        signature = base64.urlsafe_b64encode(
            base64.decodestring(res['signature']))
        signed_jwt = '{}.{}'.format(header_and_payload, signature)
    
        return signed_jwt

    Replace aud with https://SERVICE_NAME where SERVICE_NAME is the value of the host entry in the API configuration file.

  2. Send the JWT in a request:

    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(HOST)
        conn.request("GET", '/auth/info/googlejwt', None, headers)
        res = conn.getresponse()
        conn.close()
        return res.read()

key file

If you want to manage your own keys and key rotation, you can sign the JWT with the default or non-default service account's private key file.

To use a JWT signed with a key file:

  1. In the calling service, add code that creates a JWT signed by the private key file:

    def generate_jwt(service_account_file):
        """Generates a signed JSON Web Token using a Google API Service Account."""
    
        # Note: this sample shows how to manually create the JWT for the purposes
        # of showing how the authentication works, but you can use
        # google.auth.jwt.Credentials to automatically create the JWT.
        #   http://google-auth.readthedocs.io/en/latest/reference
        #   /google.auth.jwt.html#google.auth.jwt.Credentials
    
        signer = google.auth.crypt.RSASigner.from_service_account_file(
            service_account_file)
    
        now = int(time.time())
        expires = now + 3600  # One hour in seconds
    
        payload = {
            'iat': now,
            'exp': expires,
            # aud must match 'audience' in the security configuration in your
            # swagger spec. It can be any string.
            'aud': 'echo.endpoints.sample.google.com',
            # iss must match 'issuer' in the security configuration in your
            # swagger spec. It can be any string.
            'iss': 'jwt-client.endpoints.sample.google.com',
            # sub and email are mapped to the user id and email respectively.
            'sub': '12345678',
            'email': 'user@example.com'
        }
    
        jwt = google.auth.jwt.encode(signer, payload)
    
        return jwt

    Replace aud with https://SERVICE_NAME where SERVICE_NAME is the value of the host entry in the API configuration file.

  2. Using the key-file signed JWT, send a request to the API:

    def make_request(host, api_key, signed_jwt):
        """Makes a request to the auth info endpoint for Google JWTs."""
        url = urllib.parse.urljoin(host, '/auth/info/googlejwt')
        params = {
            'key': api_key
        }
        headers = {
            'Authorization': 'Bearer {}'.format(signed_jwt)
        }
    
        response = requests.get(url, params=params, headers=headers)
    
        response.raise_for_status()
        return response.text

Using a Google ID token

The following tabs shows code that creates a JWT, signs it, uses the signed JWT to obtain a Google ID token, and then uses that token to make an API request. The tabs show how to do this for:

  • A default service account
  • A non default service account
  • A key file

default serv acc't

To use a default service account with Google ID tokens:

  1. Create a JWT signed by the service account:

    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,
            # target_audience is the URL of the target service.
            "target_audience": TARGET_AUD,
            # aud must be Google token endpoints URL.
            "aud": "https://www.googleapis.com/oauth2/v4/token"
        })
    
        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

  2. In the above block of code, replace payload_json = json.dumps, with the following:

    payload_json = json.dumps({
        'iat': now,
        # expires after one hour.
        "exp": now + 3600,
        # iss is the service account email.
        'iss': SERVICE_ACCOUNT_EMAIL,
        # target_audience is the URL of the target service.
        "target_audience": TARGET_AUD,
        # aud must be Google token endpoints URL.
        "aud": "https://www.googleapis.com/oauth2/v4/token"
    })
    

    For iss, specify the service account email, for example, iss`: [YOUR-SERVER-PROJECT-ID]@appspot.gserviceaccount.com. Replace TARGET_AUD with https://[SERVICE_NAME], where [SERVICE_NAME] is the value of the host entry in the API configuration file, for example, YOUR-SERVER-PROJECT-ID.appspot.com.

  3. Using the JWT, send a request to the Google Token endpoints https://www.googleapis.com/oauth2/v4/token to get a Google ID to use with your API request:

    def get_id_token():
        """Request a Google ID token using a JWT."""
        params = urllib.urlencode({
            'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion': generate_jwt()})
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        conn = httplib.HTTPSConnection("www.googleapis.com")
        conn.request("POST", "/oauth2/v4/token", params, headers)
        res = json.loads(conn.getresponse().read())
        conn.close()
        return res['id_token']

  4. Send a request to the API, using the ID token:

    def make_request(token):
        """Makes a request to the auth info endpoint for Google ID token."""
        headers = {'Authorization': 'Bearer {}'.format(token)}
        conn = httplib.HTTPSConnection(HOST)
        conn.request("GET", '/auth/info/googleidtoken', None, headers)
        res = conn.getresponse()
        conn.close()
        return res.read()

non-default

To use a non-default service account with Google ID tokens:

  1. Create a JWT signed by the service account:

    def generate_jwt():
        """Generates a signed JSON Web Token using a service account."""
        credentials = google.auth.app_engine.Credentials(
            scopes=['https://www.googleapis.com/auth/iam'])
        service = googleapiclient.discovery.build(
            serviceName='iam', version='v1', credentials=credentials)
    
        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,
            # aud must match 'audience' in the security configuration in your
            # swagger spec.It can be any string.
            'aud': 'echo.endpoints.sample.google.com',
            "email": SERVICE_ACCOUNT_EMAIL
        })
    
        header_and_payload = '{}.{}'.format(
            base64.urlsafe_b64encode(header_json),
            base64.urlsafe_b64encode(payload_json))
        slist = service.projects().serviceAccounts().signBlob(
            name=SERVICE_ACCOUNT,
            body={'bytesToSign': base64.b64encode(header_and_payload)})
        res = slist.execute()
        signature = base64.urlsafe_b64encode(
            base64.decodestring(res['signature']))
        signed_jwt = '{}.{}'.format(header_and_payload, signature)
    
        return signed_jwt

  2. In the above block of code, replace payload_json = json.dumps, with the following:

    payload_json = json.dumps({
        'iat': now,
        # expires after one hour.
        "exp": now + 3600,
        # iss is the service account email.
        'iss': SERVICE_ACCOUNT_EMAIL,
        # target_audience is the URL of the target service.
        "target_audience": TARGET_AUD,
        # aud must be Google token endpoints URL.
        "aud": "https://www.googleapis.com/oauth2/v4/token"
    })
    

    For iss, specify the service account email. Replace TARGET_AUD with https://[SERVICE_NAME], where [SERVICE_NAME] is the value of the host entry in the API configuration file, for example, YOUR-SERVER-PROJECT-ID.appspot.com.

  3. Using the JWT, send a request to the Google Token endpoints https://www.googleapis.com/oauth2/v4/token to get a Google ID to use with your API request:

    def get_id_token():
        """Request a Google ID token using a JWT."""
        params = urllib.urlencode({
            'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion': generate_jwt()})
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        conn = httplib.HTTPSConnection("www.googleapis.com")
        conn.request("POST", "/oauth2/v4/token", params, headers)
        res = json.loads(conn.getresponse().read())
        conn.close()
        return res['id_token']

  4. Send a request to the API, using the ID token:

    def make_request(token):
        """Makes a request to the auth info endpoint for Google ID token."""
        headers = {'Authorization': 'Bearer {}'.format(token)}
        conn = httplib.HTTPSConnection(HOST)
        conn.request("GET", '/auth/info/googleidtoken', None, headers)
        res = conn.getresponse()
        conn.close()
        return res.read()

key file

If you want to manage your own keys and key rotation, you can sign the JWT with the (default or non-default) service account's private key file.

To use a key file:

  1. Create a JWT signed by the private key file:

    def generate_jwt(service_account_file):
        """Generates a signed JSON Web Token using a Google API Service Account."""
    
        # Note: this sample shows how to manually create the JWT for the purposes
        # of showing how the authentication works, but you can use
        # google.auth.jwt.Credentials to automatically create the JWT.
        #   http://google-auth.readthedocs.io/en/latest/reference
        #   /google.auth.jwt.html#google.auth.jwt.Credentials
    
        signer = google.auth.crypt.RSASigner.from_service_account_file(
            service_account_file)
    
        now = int(time.time())
        expires = now + 3600  # One hour in seconds
    
        payload = {
            'iat': now,
            'exp': expires,
            # aud must match 'audience' in the security configuration in your
            # swagger spec. It can be any string.
            'aud': 'echo.endpoints.sample.google.com',
            # iss must match 'issuer' in the security configuration in your
            # swagger spec. It can be any string.
            'iss': 'jwt-client.endpoints.sample.google.com',
            # sub and email are mapped to the user id and email respectively.
            'sub': '12345678',
            'email': 'user@example.com'
        }
    
        jwt = google.auth.jwt.encode(signer, payload)
    
        return jwt

  2. In the above block of code, replace payload_json = json.dumps, with the following:

    payload_json = json.dumps({
        'iat': now,
        # expires after one hour.
        "exp": now + 3600,
        # iss is the service account email.
        'iss': SERVICE_ACCOUNT_EMAIL,
        # target_audience is the URL of the target service.
        "target_audience": TARGET_AUD,
        # aud must be Google token endpoints URL.
        "aud": "https://www.googleapis.com/oauth2/v4/token"
    })
    

    For iss, specify the service account email, for example, iss`: [YOUR-SERVER-PROJECT-ID]@appspot.gserviceaccount.com. Replace TARGET_AUD with https://[SERVICE_NAME], where [SERVICE_NAME] is the value of the host entry in the API configuration file, for example, YOUR-SERVER-PROJECT-ID.appspot.com.

  3. Using the key file-signed JWT, send a request to the Google Token endpoints https://www.googleapis.com/oauth2/v4/token to get a Google ID to use with your API request:

    def get_id_token():
        """Request a Google ID token using a JWT."""
        params = urllib.urlencode({
            'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion': generate_jwt()})
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        conn = httplib.HTTPSConnection("www.googleapis.com")
        conn.request("POST", "/oauth2/v4/token", params, headers)
        res = json.loads(conn.getresponse().read())
        conn.close()
        return res['id_token']

  4. Send a request to the API, using the ID token:

    def make_request(token):
        """Makes a request to the auth info endpoint for Google ID token."""
        headers = {'Authorization': 'Bearer {}'.format(token)}
        conn = httplib.HTTPSConnection(HOST)
        conn.request("GET", '/auth/info/googleidtoken', None, headers)
        res = conn.getresponse()
        conn.close()
        return res.read()

Receiving auth results in your API

Endpoints forwards all headers it receives, including the original Authorization header, to the API. In addition, Endpoints sends the authentication result in X-Endpoint-API-UserInfo header to the API. This header is base64 encoded and contains following JSON object.

{
  "issuer": TOKEN_ISSUER,     // optional
  "id": USER_ID,              // optional
  "email" : USER_EMAIL        // optional
}

For examples on how to process these headers, see the samples listed below.

Using service accounts with or without key files

Using service accounts without key files to sign your JWT allows you to take advantage of built-in key management. If you don't use a key file, Google Cloud Platform will supply and manage keys for you, maintaining and rotating internal keys for each service account. These keys are never exposed to internal or external customers.

This means that you only provide the service account name and JWT payload and Google Cloud Platform signs the JWT using one of the internal keys for that service account.

If you want to manage your own keys instead of using built-in key management, see the sample code for the google-jwt-client.

What's next

For examples that show how to send tokens and required asserts in a security header in an API request from clients, and process user information from authentication in your API, see the Cloud Endpoints samples for:

Monitor your resources on the go

Get the Google Cloud Console app to help you manage your projects.

Send feedback about...

Cloud Endpoints with OpenAPI