Programmatic Authentication

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

  • A user account belongs to an individual user. You authenticate a user account when your application requires access to Cloud IAP-secured resources on a user's behalf. Read about user account credentials.
  • A service account belongs to an application instead of an individual user. You authenticate a service account when you want to allow an application to access your Cloud IAP-secured resources. Learn how to understand service accounts.

Before you begin

Before you begin, you'll need the following:

  • A Cloud IAP-secured application to which you want to programmatically connect using a developer account, service account, or mobile app credentials.

Authenticating a user account

You can enable user access to your app from a desktop or mobile app to allow a program to interact with a Cloud IAP-secured resource.

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 Create credentials, then select OAuth Client ID.
    4. Select the Application type for which you want to create credentials.
    5. Add a Name and Restrictions if appropriate, then click Create.
  2. On the OAuth client window that appears, note the 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.

Authenticating from a desktop app

This section describes how to authenticate a user account from a desktop command line.

Setting up the client ID

To allow developers to access your application from the command line, you'll first need to create OAuth client ID credentials of type Other:

  1. Access Credentials.
  2. Select the project with the Cloud IAP-secured resource.
  3. Click Create credentials, then select OAuth Client ID.
  4. Under Application type, select Other, add a Name, then click Create.
  5. On the OAuth client window that appears, note the client ID and client secret. You'll need to use these in a script to manage credentials or otherwise share with your developers.
  6. On the Credentials window, your new Other credentials appear along with the primary client ID that's used to access your application.

Signing in to the application

Each developer who wants to access a Cloud IAP-secured app will need to sign in first. This process can be packaged into a script, such as by using Cloud SDK. Following is an example using curl to sign in and generate a token that can be used to access the application:

  1. Sign in to your account that has access to the Cloud Platform resource.
  2. Go to the following URI where OTHER_CLIENT_ID is the Other client ID you created above:

    https://accounts.google.com/o/oauth2/v2/auth?client_id=OTHER_CLIENT_ID&response_type=code&scope=openid%20email&access_type=offline&redirect_uri=urn:ietf:wg:oauth:2.0:oob

  3. In the window that appears, note the Authorization code to replace AUTH_CODE below along with the Other client ID and secret you created above:

    curl --verbose \
          --data client_id=OTHER_CLIENT_ID \
          --data client_secret=OTHER_CLIENT_SECRET \
          --data code=AUTH_CODE \
          --data redirect_uri=urn:ietf:wg:oauth:2.0:oob \
          --data grant_type=authorization_code \
          https://www.googleapis.com/oauth2/v4/token

    This code returns a JSON object with a refresh_token field that you can save as a login token to access the application.

Accessing the application

To access the application, you'll exchange the refresh_token you generated during the sign-in flow for an ID token. The ID token is valid for about one hour, during which time you can make multiple requests to a specific app. Following is an example using curl to use the token and access the application:

  1. Use the code below where REFRESH_TOKEN is the token from the sign-in flow, IAP_CLIENT_ID is the primary client ID used to access your application, and OTHER_CLIENT_ID and OTHER_CLIENT_SECRET are the client ID and secret you created when you set up the client ID above:

    curl --verbose \
          --data client_id=OTHER_CLIENT_ID \
          --data client_secret=OTHER_CLIENT_SECRET \
          --data refresh_token=REFRESH_TOKEN \
          --data grant_type=refresh_token \
          --data audience=IAP_CLIENT_ID

    This code returns a JSON object with an id_token field that you can use to access the app.

  2. To access the app, use the id_token as follows:

    curl --verbose --header 'Authorization: Bearer ID_TOKEN' ...

Authenticating from a service account

Use an OpenID Connect token to authenticate a service account to a 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 Cloud IAP-secured client ID.
    1. To find the client ID, go to the Identity-Aware Proxy interface, find the resource you want to access, click More > OAuth Client on the right side, then note the client ID on the Credentials page that appears.
  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:

Python

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


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


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

    Args:
      url: The Identity-Aware Proxy-protected URL to fetch.
      client_id: The client ID used by Identity-Aware Proxy.

    Returns:
      The page body, or raises an exception if the page couldn't be retrieved.
    """
    # 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': client_id
        })

    # 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']

Java

private static final String IAM_SCOPE = "https://www.googleapis.com/auth/iam";
private static final String OAUTH_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token";
private static final String JWT_BEARER_TOKEN_GRANT_TYPE =
    "urn:ietf:params:oauth:grant-type:jwt-bearer";
private static final long EXPIRATION_TIME_IN_SECONDS = 3600L;

private static final HttpTransport httpTransport = new NetHttpTransport();

private static Clock clock = Clock.systemUTC();

private BuildIapRequest() {}

private static ServiceAccountCredentials getCredentials() throws Exception {
  GoogleCredentials credentials =
      GoogleCredentials.getApplicationDefault().createScoped(Collections.singleton(IAM_SCOPE));
  // service account credentials are required to sign the jwt token
  if (credentials == null || !(credentials instanceof ServiceAccountCredentials)) {
    throw new Exception("Google credentials : service accounts credentials expected");
  }
  return (ServiceAccountCredentials) credentials;
}

private static String getSignedJWToken(ServiceAccountCredentials credentials, String iapClientId)
    throws IOException {
  Instant now = Instant.now(clock);
  long expirationTime = now.getEpochSecond() + EXPIRATION_TIME_IN_SECONDS;

  // generate jwt signed by service account
  return Jwts.builder()
      .setHeaderParam("kid", credentials.getPrivateKeyId())
      .setIssuer(credentials.getClientEmail())
      .setAudience(OAUTH_TOKEN_URI)
      .setSubject(credentials.getClientEmail())
      .setIssuedAt(Date.from(now))
      .setExpiration(Date.from(Instant.ofEpochSecond(expirationTime)))
      .claim("target_audience", iapClientId)
      .signWith(SignatureAlgorithm.RS256, credentials.getPrivateKey())
      .compact();
}

private static String getGoogleIdToken(String jwt) throws Exception {
  final GenericData tokenRequest =
      new GenericData().set("grant_type", JWT_BEARER_TOKEN_GRANT_TYPE).set("assertion", jwt);
  final UrlEncodedContent content = new UrlEncodedContent(tokenRequest);

  final HttpRequestFactory requestFactory = httpTransport.createRequestFactory();

  final HttpRequest request =
      requestFactory
          .buildPostRequest(new GenericUrl(OAUTH_TOKEN_URI), content)
          .setParser(new JsonObjectParser(JacksonFactory.getDefaultInstance()));

  HttpResponse response;
  String idToken = null;
  response = request.execute();
  GenericData responseData = response.parseAs(GenericData.class);
  idToken = (String) responseData.get("id_token");
  return idToken;
}

/**
 * Clone request and add an IAP Bearer Authorization header with signed JWT token.
 * @param request Request to add authorization header
 * @param iapClientId OAuth 2.0 client ID for IAP protected resource
 * @return Clone of request with Bearer style authorization header with signed jwt token.
 * @throws Exception
 */
public static HttpRequest buildIAPRequest(HttpRequest request, String iapClientId) throws Exception {
  // get service account credentials
  ServiceAccountCredentials credentials = getCredentials();
  // get the base url of the request URL
  String jwt = getSignedJWToken(credentials, iapClientId);
  if (jwt == null) {
    throw new Exception(
        "Unable to create a signed jwt token for : "
            + iapClientId
            + "with issuer : "
            + credentials.getClientEmail());
  }

  String idToken = getGoogleIdToken(jwt);
  if (idToken == null) {
    throw new Exception("Unable to retrieve open id token");
  }

  // Create an authorization header with bearer token
  HttpHeaders httpHeaders = request.getHeaders().clone().setAuthorization("Bearer " + idToken);

  // create request with jwt authorization header
  return httpTransport
      .createRequestFactory()
      .buildRequest(request.getRequestMethod(), request.getUrl(), request.getContent())
      .setHeaders(httpHeaders);
}

What's next

Send feedback about...

Identity-Aware Proxy Documentation