プログラムによる認証

このページでは、ユーザー アカウントまたはサービス アカウントから Cloud Identity-Aware Proxy(Cloud IAP)で保護されたリソースを認証する方法について説明します。

  • ユーザー アカウントは、個々のユーザーに属します。ユーザーの代わりにアプリケーションが Cloud IAP で保護されたリソースにアクセスする必要がある場合、ユーザー アカウントを認証します。ユーザー アカウント認証情報をご覧ください。
  • サービス アカウントは、個々のユーザーではなくアプリケーションに属します。アプリケーションから Cloud IAP で保護されたリソースにアクセスできるようにする場合、サービス アカウントを認証します。サービス アカウントについての説明をご覧ください。

始める前に

始める前に、次のものが必要になります。

  • デベロッパー アカウント、サービス アカウント、モバイルアプリの認証情報を使用してプログラムで接続する、Cloud IAP で保護されたアプリケーション。

ユーザー アカウントの認証

デスクトップまたはモバイルアプリからアプリへのユーザー アクセスを有効にすると、プログラムから Cloud IAP で保護されたリソースを操作できます。

モバイルアプリからの認証

  1. Cloud IAP で保護されたリソースと同じプロジェクトで、モバイルアプリの OAuth 2.0 クライアント ID を作成します。
    1. [認証情報] ページに移動します。
      [認証情報] ページに移動
    2. Cloud IAP で保護されたリソースを含むプロジェクトを選択します。
    3. [認証情報を作成] をクリックし、[OAuth クライアント ID] を選択します。
    4. 認証情報を作成する [アプリケーションの種類] を選択します。
    5. 必要に応じて [名前] と [制限事項] を追加し、[作成] をクリックします。
  2. 表示される [OAuth クライアント] ウィンドウで、接続先の Cloud IAP で保護されたリソースの [クライアント ID] をメモします。
  3. Cloud IAP で保護されたクライアント ID の ID トークンを取得します。
  4. Authorization: Bearer ヘッダーに ID トークンを追加して、Cloud IAP で保護されたリソースへの認証済みリクエストを作成します。

デスクトップ アプリからの認証

このセクションでは、デスクトップ コマンドラインからユーザー アカウントを認証する方法について説明します。

クライアント ID の設定

デベロッパーがコマンドラインからアプリケーションにアクセスできるようにするには、まず [その他] タイプの OAuth クライアント ID 認証情報を作成する必要があります。

  1. [認証情報] ページに移動します。
    [認証情報] ページに移動
  2. Cloud IAP で保護されたリソースを含むプロジェクトを選択します。
  3. [認証情報を作成] をクリックし、[OAuth クライアント ID] を選択します。
  4. [アプリケーションの種類] で [その他] を選択し、[名前] を追加して、[作成] をクリックします。
  5. 表示される [OAuth クライアント] ウィンドウで、[クライアント ID] と [クライアント シークレット] をメモします。認証情報を管理したり、デベロッパーと共有したりするには、これらをスクリプトで使用する必要があります。
  6. [認証情報] ウィンドウに、アプリケーションへのアクセスに使用するメイン クライアント ID とともに、新しい [その他] の認証情報が表示されます。

アプリケーションへのログイン

個々のデベロッパーが Cloud IAP で保護されたアプリにアクセスするには、まずログインする必要があります。Cloud SDK を使用するなどの方法で、プロセスをスクリプトにパッケージ化できます。curl を使用してログインし、アプリケーションにアクセスするために使用できるトークンを生成する例を次に示します。

  1. GCP リソースにアクセスできるアカウントにログインします。
  2. 下記の URI に移動します。OTHER_CLIENT_ID は、上記で作成した [その他] のクライアント ID です。

    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. 表示されるウィンドウで、次に示す AUTH_CODE を置き換える [認証コード] と、上記で作成した [その他] のクライアント ID とシークレットをメモします。

    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://oauth2.googleapis.com/token

    このコードは、アプリケーションにアクセスするためのログイン トークンとして保存できる refresh_token フィールドを含む JSON オブジェクトを返します。

アプリケーションへのアクセス

アプリケーションにアクセスするには、ID トークンのログインフローで生成した refresh_token を交換します。ID トークンは約 1 時間有効です。その間、特定のアプリに対して複数のリクエストを行うことができます。curl を使用してトークンを使用し、アプリケーションにアクセスする例を次に示します。

  1. 以下のコードを使用します。REFRESH_TOKEN はログインフローで生成したトークン、IAP_CLIENT_ID はアプリケーションへのアクセスに使用するメイン クライアント ID、OTHER_CLIENT_IDOTHER_CLIENT_SECRET は上記のクライアント ID の設定時に作成したクライアント ID とシークレットです。

    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 \
          https://oauth2.googleapis.com/token

    このコードは、アプリにアクセスするために使用できる id_token フィールドを含む JSON オブジェクトを返します。

  2. アプリにアクセスするには、次のように id_token を使用します。

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

サービス アカウントからの認証

OpenID Connect(OIDC)トークンを使用して、Cloud IAP で保護されたリソースに対してサービス アカウントを認証します。

  1. Cloud IAP で保護されたプロジェクトのアクセスリストにサービス アカウントを追加します。
  2. JWT ベースのアクセス トークンを生成します。これは、クライアント ID を必要とする target_audience 追加要求を使用します。クライアント ID を確認する手順は次のとおりです。

    1. [Cloud IAP] ページに移動します。
    2. アクセスするリソースを見つけて、[詳細] > [OAuth クライアントを編集] をクリックします。
      [その他] メニューで OAuth クライアントを編集する

    3. 表示される [認証情報] ページで、クライアント ID をメモします。

  3. Cloud IAP で保護されたクライアント ID の OIDC トークンをリクエストします。

  4. Authorization: Bearer ヘッダーに OIDC トークンを追加して、Cloud IAP で保護されたリソースへの認証済みリクエストを作成します。

次のサンプルコードは、Cloud IAP で保護されたリソースに対して既定のサービス アカウントを認証します。

C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Requests;
using Google.Apis.Json;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;

namespace GoogleCloudSamples
{
    class IAPClient
    {
        /// <summary>
        /// Authenticates using the client id and credentials, then fetches
        /// the uri.
        /// </summary>
        /// <param name="iapClientId">The client id observed on
        /// https://console.cloud.google.com/apis/credentials.</param>
        /// <param name="credentialsFilePath">Path to the credentials .json file
        /// download from https://console.cloud.google.com/apis/credentials.
        /// </param>
        /// <param name="uri">HTTP uri to fetch.</param>
        /// <returns>The http response body as a string.</returns>
        public static string InvokeRequest(string iapClientId,
            string credentialsFilePath, string uri)
        {
            // Read credentials from the credentials .json file.
            ServiceAccountCredential saCredential;
            using (var fs = new FileStream(credentialsFilePath,
                FileMode.Open, FileAccess.Read))
            {
                saCredential = ServiceAccountCredential
                    .FromServiceAccountData(fs);
            }

            // Generate a JWT signed with the service account's private key
            // containing a special "target_audience" claim.
            var jwtBasedAccessToken =
                CreateAccessToken(saCredential, iapClientId);

            // Request an OIDC token for the Cloud IAP-secured client ID.
            var req = new GoogleAssertionTokenRequest()
            {
                Assertion = jwtBasedAccessToken
            };
            var result = req.ExecuteAsync(saCredential.HttpClient,
                saCredential.TokenServerUrl, CancellationToken.None,
                saCredential.Clock).Result;
            string token = result.IdToken;

            // Include the OIDC token in an Authorization: Bearer header to
            // IAP-secured resource
            var httpClient = new HttpClient();
            httpClient.DefaultRequestHeaders.Authorization =
                new AuthenticationHeaderValue("Bearer", token);
            string response = httpClient.GetStringAsync(uri).Result;
            return response;
        }

        /// <summary>
        /// Generate a JWT signed with the service account's private key
        /// containing a special "target_audience" claim.
        /// </summary>
        /// <param name="privateKey">The private key string pulled from
        /// a credentials .json file.</param>
        /// <param name="iapClientId">The client id observed on
        /// https://console.cloud.google.com/apis/credentials.</param>
        /// <param name="email">The e-mail address associated with the
        /// privateKey.</param>
        /// <returns>An access token.</returns>
        static string CreateAccessToken(ServiceAccountCredential saCredential,
            string iapClientId)
        {
            var now = saCredential.Clock.UtcNow;
            var currentTime = ToUnixEpochDate(now);
            var expTime = ToUnixEpochDate(now.AddHours(1));

            var claims = new[]
            {
                new Claim(JwtRegisteredClaimNames.Aud,
                    GoogleAuthConsts.OidcTokenUrl),
                new Claim(JwtRegisteredClaimNames.Sub, saCredential.Id),
                new Claim(JwtRegisteredClaimNames.Iat, currentTime.ToString()),
                new Claim(JwtRegisteredClaimNames.Exp, expTime.ToString()),
                new Claim(JwtRegisteredClaimNames.Iss, saCredential.Id),

                // We need to generate a JWT signed with the service account's
                // private key containing a special "target_audience" claim.
                // That claim should contain the clientId of IAP we eventually
                // want to access.
                new Claim("target_audience", iapClientId)
            };

            // Encryption algorithm must be RSA SHA-256, according to
            // https://developers.google.com/identity/protocols/OAuth2ServiceAccount
            var signingCredentials = new SigningCredentials(
                new RsaSecurityKey(saCredential.Key),
                SecurityAlgorithms.RsaSha256);
            var token = new JwtSecurityToken(
                claims: claims,
                signingCredentials: signingCredentials);
            return new JwtSecurityTokenHandler().WriteToken(token);
        }

        static long ToUnixEpochDate(DateTime date)
              => (long)Math.Round((date.ToUniversalTime() -
                                   new DateTimeOffset(1970, 1, 1, 0, 0, 0,
                                        TimeSpan.Zero)).TotalSeconds);
    }
}

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, method='GET', **kwargs):
    """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.
      method: The request method to use
              ('GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE')
      **kwargs: Any of the parameters defined for the request function:
                https://github.com/requests/requests/blob/master/requests/api.py
                If no timeout is provided, it is set to 90 by default.

    Returns:
      The page body, or raises an exception if the page couldn't be retrieved.
    """
    # Set the default timeout, if missing
    if 'timeout' not in kwargs:
        kwargs['timeout'] = 90

    # 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.request(
        method, url,
        headers={'Authorization': 'Bearer {}'.format(
            google_open_id_connect_token)}, **kwargs)
    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

import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.UrlEncodedContent;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.GenericData;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import java.time.Clock;
import java.time.Instant;
import java.util.Collections;
import java.util.Date;

public class BuildIapRequest {
  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 getSignedJwt(ServiceAccountCredentials credentials, String iapClientId)
      throws Exception {
    Instant now = Instant.now(clock);
    long expirationTime = now.getEpochSecond() + EXPIRATION_TIME_IN_SECONDS;

    // generate jwt signed by service account
    // header must contain algorithm ("alg") and key ID ("kid")
    JWSHeader jwsHeader =
        new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(credentials.getPrivateKeyId()).build();

    // set required claims
    JWTClaimsSet claims =
        new JWTClaimsSet.Builder()
            .audience(OAUTH_TOKEN_URI)
            .issuer(credentials.getClientEmail())
            .subject(credentials.getClientEmail())
            .issueTime(Date.from(now))
            .expirationTime(Date.from(Instant.ofEpochSecond(expirationTime)))
            .claim("target_audience", iapClientId)
            .build();

    // sign using service account private key
    JWSSigner signer = new RSASSASigner(credentials.getPrivateKey());
    SignedJWT signedJwt = new SignedJWT(jwsHeader, claims);
    signedJwt.sign(signer);

    return signedJwt.serialize();
  }

  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 exception creating signed JWT
   */
  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 = getSignedJwt(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);
  }
}

PHP

namespace Google\Cloud\Samples\Iap;

# Imports Auth libraries and Guzzle HTTP libraries.
use Google\Auth\OAuth2;
use Google\Auth\Middleware\ScopedAccessTokenMiddleware;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;

/**
 * Make a request to an application protected by Identity-Aware Proxy.
 *
 * @param string $url The Identity-Aware Proxy-protected URL to fetch.
 * @param string $clientId The client ID used by Identity-Aware Proxy.
 *
 * @return The response body.
 */
function make_iap_request($url, $clientId, $pathToServiceAccount)
{
    $serviceAccountKey = json_decode(file_get_contents($pathToServiceAccount), true);
    $oauth_token_uri = 'https://www.googleapis.com/oauth2/v4/token';
    $iam_scope = 'https://www.googleapis.com/auth/iam';

    # Create an OAuth object using the service account key
    $oauth = new OAuth2([
        'audience' => $oauth_token_uri,
        'issuer' => $serviceAccountKey['client_email'],
        'signingAlgorithm' => 'RS256',
        'signingKey' => $serviceAccountKey['private_key'],
        'tokenCredentialUri' => $oauth_token_uri,
    ]);
    $oauth->setGrantType(OAuth2::JWT_URN);
    $oauth->setAdditionalClaims(['target_audience' => $clientId]);

    # Obtain an OpenID Connect token, which is a JWT signed by Google.
    $token = $oauth->fetchAuthToken();
    $idToken = $oauth->getIdToken();

    # Construct a ScopedAccessTokenMiddleware with the ID token.
    $middleware = new ScopedAccessTokenMiddleware(
        function () use ($idToken) {
            return $idToken;
        },
        $iam_scope
    );

    $stack = HandlerStack::create();
    $stack->push($middleware);

    # Create an HTTP Client using Guzzle and pass in the credentials.
    $http_client = new Client([
        'handler' => $stack,
        'base_uri' => $url,
        'auth' => 'scoped'
    ]);

    # Make an authenticated HTTP Request
    $response = $http_client->request('GET', '/', []);
    return $response;
}

次のステップ

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Identity-Aware Proxy のドキュメント