プログラムによる認証

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

  • ユーザー アカウントは、個々のユーザーに属します。ユーザーの代わりにアプリケーションが IAP で保護されたリソースにアクセスする必要がある場合、ユーザー アカウントを認証します。詳しくは、ユーザー アカウントをご覧ください。

  • サービス アカウントは、個々のユーザーではなくアプリケーションに属します。アプリケーションから IAP で保護されたリソースにアクセスできるようにする場合、サービス アカウントを認証します。詳しくは、サービス アカウントをご覧ください。

始める前に

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

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

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

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

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

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

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

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

クライアント ID の設定

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

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

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

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

  1. Google Cloud リソースにアクセスできるアカウントにログインします。
  2. 受信リクエストをエコーできるローカル サーバーを起動します。
        $ nc -k -l 4444
        
    注: このコマンドでは NetCat ユーティリティを使用します。 任意のユーティリティを使用できます。
  3. 下記の URI に移動します。DESKTOP_CLIENT_ID は、上記で作成した [デスクトップ アプリ] のクライアント ID です。

    https://accounts.google.com/o/oauth2/v2/auth?client_id=DESKTOP_CLIENT_ID&response_type=code&scope=openid%20email&access_type=offline&redirect_uri=http://localhost:4444&cred_ref=true

  4. ローカル サーバーの出力で、リクエスト パラメータを探します。次のようになります。 GET /?code=$CODE&scope=email%20openid%20https://www.googleapis.com/auth/userinfo.email&hd=google.com&prompt=consent HTTP/1.1 コードをコピーし、以下の AUTH_CODE を上記で作成したデスクトップ アプリのクライアント ID とシークレットに置き換えます。IAP_CLIENT_ID は、アプリケーションへのアクセスに使用するメイン クライアント ID です。

    curl --verbose \
          --data client_id=DESKTOP_CLIENT_ID \
          --data client_secret=DESKTOP_CLIENT_SECRET \
          --data code=AUTH_CODE \
          --data audience=IAP_CLIENT_ID \
          --data redirect_uri=http://localhost:4444 \
          --data grant_type=authorization_code \
          https://oauth2.googleapis.com/token

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

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

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

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

トークンを更新

ログインフロー中に生成された更新トークンを使用して、新しい ID トークンを取得できます。これは、元の ID トークンが期限切れになった際に役立ちます。各 ID トークンは約 1 時間有効です。その間、特定のアプリに対して複数のリクエストを行うことができます。

次の curl を使用する例では、更新トークンを使用して新しい ID トークンを取得します。次の例では、REFRESH_TOKEN はログインフローで生成したトークンです。IAP_CLIENT_IDDESKTOP_CLIENT_IDDESKTOP_CLIENT_SECRET は、ログインフローで使用されるものと同じです。

curl --verbose \
--data client_id=DESKTOP_CLIENT_ID \
--data client_secret=DESKTOP_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 オブジェクトを返します。

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

OpenID Connect(OIDC)トークンを使用して、IAP で保護されたリソースに対してサービス アカウントを認証します。以下の手順に沿って、クライアント ID を確認します。

  1. IAP ページに移動します。
  2. アクセスするリソースを見つけて、 > [OAuth 構成に移動] をクリックします。
    [その他] メニューで OAuth の構成に移動

  3. 表示されるページで、クライアント ID をメモします。 バックエンド サービスのクライアント ID を変更するには、gcloud compute backend-services update コマンドを使用します。

    gcloud compute backend-services update BACKEND_SERVICE_NAME \
        --iap='enabled,oauth2-client-id=OAUTH_CLIENT_ID,oauth2-client-secret=OAUTH_CLIENT_SECRET'

    以下を置き換えます。

    • BACKEND_SERVICE_NAME: バックエンド サービス アカウントの名前。
    • OAUTH_CLIENT_ID: 新しい OAuth クライアント ID。
    • OAUTH_CLIENT_SECRET: 新しい OAuth クライアント シークレット。
    複数のバックエンドに同じ OAuth クライアントを使用する場合にも便利です。

また、IAP で保護されたプロジェクトのアクセスリストにサービス アカウントを追加する必要もあります。次のコードサンプルは、OIDC トークンを取得する方法を示しています。いずれのオプションを選択する場合でも、IAP で保護されたリソースに認証済みリクエストを送信するには、Authorization: Bearer ヘッダーにトークンを含めます。

デフォルトのサービス アカウント用に OIDC トークンを取得する

Compute Engine、App Engine、Cloud Run のデフォルトのサービス アカウント用 OIDC トークンを取得する場合は、以下のコードサンプルを使用してアクセス トークンを生成し、IAP で保護されたリソースにアクセスします。

C#


using Google.Apis.Auth.OAuth2;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

public class IAPClient
{
    /// <summary>
    /// Makes a request to a IAP secured application by first obtaining
    /// an OIDC token.
    /// </summary>
    /// <param name="iapClientId">The client ID observed on
    /// https://console.cloud.google.com/apis/credentials. </param>
    /// <param name="uri">HTTP URI to fetch.</param>
    /// <param name="cancellationToken">The token to propagate operation cancel notifications.</param>
    /// <returns>The HTTP response message.</returns>
    public async Task<HttpResponseMessage> InvokeRequestAsync(
        string iapClientId, string uri, CancellationToken cancellationToken = default)
    {
        // Get the OidcToken.
        // You only need to do this once in your application
        // as long as you can keep a reference to the returned OidcToken.
        OidcToken oidcToken = await GetOidcTokenAsync(iapClientId, cancellationToken);

        // Before making an HTTP request, always obtain the string token from the OIDC token,
        // the OIDC token will refresh the string token if it expires.
        string token = await oidcToken.GetAccessTokenAsync(cancellationToken);

        // Include the OIDC token in an Authorization: Bearer header to
        // IAP-secured resource
        // Note: Normally you would use an HttpClientFactory to build the httpClient.
        // For simplicity we are building the HttpClient directly.
        using HttpClient httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
        return await httpClient.GetAsync(uri, cancellationToken);
    }

    /// <summary>
    /// Obtains an OIDC token for authentication an IAP request.
    /// </summary>
    /// <param name="iapClientId">The client ID observed on
    /// https://console.cloud.google.com/apis/credentials. </param>
    /// <param name="cancellationToken">The token to propagate operation cancel notifications.</param>
    /// <returns>The HTTP response message.</returns>
    public async Task<OidcToken> GetOidcTokenAsync(string iapClientId, CancellationToken cancellationToken)
    {
        // Obtain the application default credentials.
        GoogleCredential credential = await GoogleCredential.GetApplicationDefaultAsync(cancellationToken);

        // Request an OIDC token for the Cloud IAP-secured client ID.
       return await credential.GetOidcTokenAsync(OidcTokenOptions.FromTargetAudience(iapClientId), cancellationToken);
    }
}

Go

IAP への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。

import (
	"context"
	"fmt"
	"io"
	"net/http"

	"google.golang.org/api/idtoken"
)

// makeIAPRequest makes a request to an application protected by Identity-Aware
// Proxy with the given audience.
func makeIAPRequest(w io.Writer, request *http.Request, audience string) error {
	// request, err := http.NewRequest("GET", "http://example.com", nil)
	// audience := "IAP_CLIENT_ID.apps.googleusercontent.com"
	ctx := context.Background()

	// client is a http.Client that automatically adds an "Authorization" header
	// to any requests made.
	client, err := idtoken.NewClient(ctx, audience)
	if err != nil {
		return fmt.Errorf("idtoken.NewClient: %w", err)
	}

	response, err := client.Do(request)
	if err != nil {
		return fmt.Errorf("client.Do: %w", err)
	}
	defer response.Body.Close()
	if _, err := io.Copy(w, response.Body); err != nil {
		return fmt.Errorf("io.Copy: %w", err)
	}

	return nil
}

Java


import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.IdTokenCredentials;
import com.google.auth.oauth2.IdTokenProvider;
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.util.Collections;

public class BuildIapRequest {
  private static final String IAM_SCOPE = "https://www.googleapis.com/auth/iam";

  private static final HttpTransport httpTransport = new NetHttpTransport();

  private BuildIapRequest() {}

  private static IdTokenProvider getIdTokenProvider() throws IOException {
    GoogleCredentials credentials =
        GoogleCredentials.getApplicationDefault().createScoped(Collections.singleton(IAM_SCOPE));

    Preconditions.checkNotNull(credentials, "Expected to load credentials");
    Preconditions.checkState(
        credentials instanceof IdTokenProvider,
        String.format(
            "Expected credentials that can provide id tokens, got %s instead",
            credentials.getClass().getName()));

    return (IdTokenProvider) credentials;
  }

  /**
   * 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 IOException exception creating signed JWT
   */
  public static HttpRequest buildIapRequest(HttpRequest request, String iapClientId)
      throws IOException {

    IdTokenProvider idTokenProvider = getIdTokenProvider();
    IdTokenCredentials credentials =
        IdTokenCredentials.newBuilder()
            .setIdTokenProvider(idTokenProvider)
            .setTargetAudience(iapClientId)
            .build();

    HttpRequestInitializer httpRequestInitializer = new HttpCredentialsAdapter(credentials);

    return httpTransport
        .createRequestFactory(httpRequestInitializer)
        .buildRequest(request.getRequestMethod(), request.getUrl(), request.getContent());
  }
}

Node.js

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const url = 'https://some.iap.url';
// const targetAudience = 'IAP_CLIENT_ID.apps.googleusercontent.com';

const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();

async function request() {
  console.info(`request IAP ${url} with target audience ${targetAudience}`);
  const client = await auth.getIdTokenClient(targetAudience);
  const res = await client.request({url});
  console.info(res.data);
}

request().catch(err => {
  console.error(err.message);
  process.exitCode = 1;
});

PHP

IAP への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。

namespace Google\Cloud\Samples\Iap;

# Imports Auth libraries and Guzzle HTTP libraries.
use Google\Auth\ApplicationDefaultCredentials;
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.
 */
function make_iap_request($url, $clientId)
{
    // create middleware, using the client ID as the target audience for IAP
    $middleware = ApplicationDefaultCredentials::getIdTokenMiddleware($clientId);
    $stack = HandlerStack::create();
    $stack->push($middleware);

    // create the HTTP client
    $client = new Client([
        'handler' => $stack,
        'auth' => 'google_auth'
    ]);

    // make the request
    $response = $client->get($url);
    print('Printing out response body:');
    print($response->getBody());
}

Python

IAP への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。

from google.auth.transport.requests import Request
from google.oauth2 import id_token
import requests

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

    # Obtain an OpenID Connect (OIDC) token from metadata server or using service
    # account.
    open_id_connect_token = id_token.fetch_id_token(Request(), client_id)

    # 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(
            open_id_connect_token)}, **kwargs)
    if resp.status_code == 403:
        raise Exception('Service account does not have permission to '
                        'access the IAP-protected application.')
    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

Ruby

IAP への認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。

# url = "The Identity-Aware Proxy-protected URL to fetch"
# client_id = "The client ID used by Identity-Aware Proxy"
require "googleauth"
require "faraday"

# The client ID as the target audience for IAP
id_token_creds = Google::Auth::Credentials.default target_audience: client_id

headers = {}
id_token_creds.client.apply! headers

resp = Faraday.get url, nil, headers

if resp.status == 200
  puts "X-Goog-Iap-Jwt-Assertion:"
  puts resp.body
else
  puts "Error requesting IAP"
  puts resp.status
  puts resp.headers
end

ローカルのサービス アカウント キー ファイルから OIDC トークンを取得する

サービス アカウント キー ファイルがある場合は、上記のサンプルコードでサービス アカウント キー ファイルを指定できます。

Bash

  #!/usr/bin/env bash
  set -euo pipefail

  get_token() {
    # Get the bearer token in exchange for the service account credentials.
    local service_account_key_file_path="${1}"
    local iap_client_id="${2}"

    local iam_scope="https://www.googleapis.com/auth/iam"
    local oauth_token_uri="https://www.googleapis.com/oauth2/v4/token"

    local private_key_id="$(cat "${service_account_key_file_path}" | jq -r '.private_key_id')"
    local client_email="$(cat "${service_account_key_file_path}" | jq -r '.client_email')"
    local private_key="$(cat "${service_account_key_file_path}" | jq -r '.private_key')"
    local issued_at="$(date +%s)"
    local expires_at="$((issued_at + 3600))"
    local header="{'alg':'RS256','typ':'JWT','kid':'${private_key_id}'}"
    local header_base64="$(echo "${header}" | base64)"
    local payload="{'iss':'${client_email}','aud':'${oauth_token_uri}','exp':${expires_at},'iat':${issued_at},'sub':'${client_email}','target_audience':'${iap_client_id}'}"
    local payload_base64="$(echo "${payload}" | base64)"
    local signature_base64="$(printf %s "${header_base64}.${payload_base64}" | openssl dgst -binary -sha256 -sign <(printf '%s\n' "${private_key}")  | base64)"
    local assertion="${header_base64}.${payload_base64}.${signature_base64}"
    local token_payload="$(curl -s \
      --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
      --data-urlencode "assertion=${assertion}" \
      https://www.googleapis.com/oauth2/v4/token)"
    local bearer_id_token="$(echo "${token_payload}" | jq -r '.id_token')"
    echo "${bearer_id_token}"
  }

  main(){
    # TODO: Replace the following variables:
    SERVICE_ACCOUNT_KEY="service_account_key_file_path"
    IAP_CLIENT_ID="iap_client_id"
    URL="application_url"

    # Obtain the ID token.
    ID_TOKEN=$(get_token "${SERVICE_ACCOUNT_KEY}" "${IAP_CLIENT_ID}")
    # Access the application with the ID token.
    curl --header "Authorization: Bearer ${ID_TOKEN}" "${URL}"
  }

  main "$@"

OIDC トークンを取得するほかのケース

上記以外の場合は、IAP で保護されたリソースにアクセスする直前に IAM 認証情報 API を使用し、ターゲット サービス アカウントになりすまして OIDC トークンを生成します。このプロセスには、次のステップが含まれます。

  1. 呼び出し側のサービス アカウント(ID トークンを取得するコードに関連付けられたサービス アカウント)に、サービス アカウントの OpenID Connect ID トークン作成者ロール(roles/iam.serviceAccountOpenIdTokenCreator)を付与します。

    これにより、呼び出し元のサービス アカウントが、ターゲット サービス アカウントの権限を借用できるようになります。

  2. 呼び出し側のサービス アカウントが提供する認証情報を使用して、ターゲット サービス アカウントの generateIdToken メソッドを呼び出します。

    audience フィールドにクライアント ID を設定します。

手順については、ID トークンを作成するをご覧ください。

Proxy-Authorization ヘッダーからの認証

アプリケーションが Authorization リクエスト ヘッダーを占有する場合は、代わりに Proxy-Authorization: Bearer ヘッダーに ID トークンを含めることができます。有効な ID トークンが Proxy-Authorization ヘッダーで見つかった場合、IAP はそのトークンを使用してリクエストを承認します。リクエストを承認すると、IAP はコンテンツを処理せずに Authorization ヘッダーをアプリケーションに渡します。

Proxy-Authorization ヘッダーに有効な ID トークンが見つからない場合、IAP は Authorization ヘッダーの処理を続行し、Proxy-Authorization ヘッダーを削除してから、リクエストをアプリケーションに渡します。

次のステップ