Authenticating from a Service Account or Mobile App

This page describes how to authenticate to an Cloud Identity-Aware Proxy (Cloud IAP)-secured resource from a service account or mobile app.

Before you begin

Before you begin, you'll need the following:

  • An Cloud IAP-secured project.
  • An application you want to programmatically connect using service account or mobile app credentials.

Authenticating from a service account

Use an OpenID Connect token to authenticate a service account to an Cloud IAP-secured resource.

  1. Add the service account to the access list for the Cloud IAP-secured project.
  2. Generate a JWT-based access token (JWT-bAT).
  3. Request an OpenID Connect (OIDC) token for the resource-specific domain or the Cloud IAP-secured project ID.
    1. To find the project ID, go to your GCP Dashboard, select the relevant project, then note the ID on the Project card.
  4. Include the OIDC token in an Authorization: Bearer header to make the authenticated request to the Cloud IAP-secured resource.

Here is some sample code to authenticate the default service account to a Cloud IAP-secured resource:

import google.auth
import google.auth.app_engine
import google.auth.compute_engine.credentials
import google.auth.iam
from google.auth.transport.requests import Request
import google.oauth2.credentials
import google.oauth2.service_account
import requests
import requests_toolbelt.adapters.appengine
from six.moves import urllib_parse as urlparse


IAM_SCOPE = 'https://www.googleapis.com/auth/iam'
OAUTH_TOKEN_URI = 'https://www.googleapis.com/oauth2/v4/token'


def make_iap_request(url):
    """Makes a request to an application protected by Identity-Aware Proxy.

    Args:
      url: The Identity-Aware Proxy-protected URL to fetch.

    Returns:
      The page body, or raises an exception if the page couldn't be retrieved.
    """
    # Take the input URL and remove everything except the protocol, domain,
    # and port. Examples:
    #   https://foo.example.com/ => https://foo.example.com
    #   https://example.com:8443/foo/bar?quuz=quux#lorem =>
    #       https://example.com:8443
    base_url = urlparse.urlunparse(
        urlparse.urlparse(url)._replace(path='', query='', fragment=''))

    # Figure out what environment we're running in and get some preliminary
    # information about the service account.
    bootstrap_credentials, _ = google.auth.default(
        scopes=[IAM_SCOPE])
    if isinstance(bootstrap_credentials,
                  google.oauth2.credentials.Credentials):
        raise Exception('make_iap_request is only supported for service '
                        'accounts.')
    elif isinstance(bootstrap_credentials,
                    google.auth.app_engine.Credentials):
        requests_toolbelt.adapters.appengine.monkeypatch()

    # For service account's using the Compute Engine metadata service,
    # service_account_email isn't available until refresh is called.
    bootstrap_credentials.refresh(Request())

    signer_email = bootstrap_credentials.service_account_email
    if isinstance(bootstrap_credentials,
                  google.auth.compute_engine.credentials.Credentials):
        # Since the Compute Engine metadata service doesn't expose the service
        # account key, we use the IAM signBlob API to sign instead.
        # In order for this to work:
        #
        # 1. Your VM needs the https://www.googleapis.com/auth/iam scope.
        #    You can specify this specific scope when creating a VM
        #    through the API or gcloud. When using Cloud Console,
        #    you'll need to specify the "full access to all Cloud APIs"
        #    scope. A VM's scopes can only be specified at creation time.
        #
        # 2. The VM's default service account needs the "Service Account Actor"
        #    role. This can be found under the "Project" category in Cloud
        #    Console, or roles/iam.serviceAccountActor in gcloud.
        signer = google.auth.iam.Signer(
            Request(), bootstrap_credentials, signer_email)
    else:
        # A Signer object can sign a JWT using the service account's key.
        signer = bootstrap_credentials.signer

    # Construct OAuth 2.0 service account credentials using the signer
    # and email acquired from the bootstrap credentials.
    service_account_credentials = google.oauth2.service_account.Credentials(
        signer, signer_email, token_uri=OAUTH_TOKEN_URI, additional_claims={
            'target_audience': base_url
        })

    # service_account_credentials gives us a JWT signed by the service
    # account. Next, we use that to obtain an OpenID Connect token,
    # which is a JWT signed by Google.
    google_open_id_connect_token = get_google_open_id_connect_token(
        service_account_credentials)

    # Fetch the Identity-Aware Proxy-protected URL, including an
    # Authorization header containing "Bearer " followed by a
    # Google-issued OpenID Connect token for the service account.
    resp = requests.get(
        url,
        headers={'Authorization': 'Bearer {}'.format(
            google_open_id_connect_token)})
    if resp.status_code == 403:
        raise Exception('Service account {} does not have permission to '
                        'access the IAP-protected application.'.format(
                            signer_email))
    elif resp.status_code != 200:
        raise Exception(
            'Bad response from application: {!r} / {!r} / {!r}'.format(
                resp.status_code, resp.headers, resp.text))
    else:
        return resp.text


def get_google_open_id_connect_token(service_account_credentials):
    """Get an OpenID Connect token issued by Google for the service account.

    This function:

      1. Generates a JWT signed with the service account's private key
         containing a special "target_audience" claim.

      2. Sends it to the OAUTH_TOKEN_URI endpoint. Because the JWT in #1
         has a target_audience claim, that endpoint will respond with
         an OpenID Connect token for the service account -- in other words,
         a JWT signed by *Google*. The aud claim in this JWT will be
         set to the value from the target_audience claim in #1.

    For more information, see
    https://developers.google.com/identity/protocols/OAuth2ServiceAccount .
    The HTTP/REST example on that page describes the JWT structure and
    demonstrates how to call the token endpoint. (The example on that page
    shows how to get an OAuth2 access token; this code is using a
    modified version of it to get an OpenID Connect token.)
    """

    service_account_jwt = (
        service_account_credentials._make_authorization_grant_assertion())
    request = google.auth.transport.requests.Request()
    body = {
        'assertion': service_account_jwt,
        'grant_type': google.oauth2._client._JWT_GRANT_TYPE,
    }
    token_response = google.oauth2._client._token_endpoint_request(
        request, OAUTH_TOKEN_URI, body)
    return token_response['id_token']

Authenticating from a mobile app

  1. Create an OAuth 2.0 client ID for your mobile app in the same project as the Cloud IAP-secured resource:
    1. Access Credentials.
    2. Select the project with the Cloud IAP-secured resource.
    3. Click Credentials, then select OAuth Client ID.
  2. On the Credentials page, note the OAuth 2.0 client ID for the Cloud IAP-secured resource you want to connect to.
  3. Use the Google SignIn API to request an OpenID Connect (OIDC) token for the Cloud IAP-secured client ID.
  4. Include the OIDC token in an Authorization: Bearer header to make the authenticated request to the Cloud IAP-secured resource.

What's next

Send feedback about...

Identity-Aware Proxy Documentation