開発者、関数、エンドユーザーの認証

デフォルトでは、プロジェクトのオーナーと編集者のみが関数の作成、更新、削除を行うことができます。これらの操作を他のユーザーまたはグループに許可するには、Identity and Access Management(IAM)を使用して、異なるメンバーロール付与します。

同様に、関数を呼び出す権限を付与または制限できます。この動作は、HTTP 関数バックグラウンド関数で異なります。

認証の一般的な使用例としては、次の 3 つがあります。

  • デベロッパーのアクセスを保護する。テスト中に関数を呼び出すユーザーを限定します。

  • 関数間のアクセスを保護する。許可された関数にのみ関数の呼び出しを許可します。

  • モバイル クライアントまたはウェブ クライアントからアプリケーションへのエンドユーザーのアクセスを保護する。

デベロッパー

デベロッパーは、関数の作成、更新、削除などの管理アクション以外に、関数の公開前に非公開で関数のテストを行います。

curl や類似のツールを使用するときに、これらをエンドユーザー リクエストとして扱い、リクエストの Authorization ヘッダーに Google OAuth トークンを含める必要があります。. たとえば、次のように gcloud でトークンを取得します。

curl https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME \
  -H "Authorization: bearer $(gcloud auth print-identity-token)"

このリクエストを機能させるには、デベロッパーに割り当てられた役割に cloudfunctions.functions.invoke 権限が含まれている必要があります。デフォルトでは、Cloud Functions 管理者と Cloud Functions デベロッパーの役割にこの権限が含まれています。役割とそれに関連する権限の詳細については、Cloud Functions IAM 役割をご覧ください。

また、関数の開発と使用に必要な最小限の権限セットを割り当てることをおすすめします。関数の IAM ポリシーは、必要最小限のユーザーとサービス アカウントに限定してください。

関数間のアクセス

複数の関数を接続するサービスを作成する場合、それぞれの関数が特定の関数にのみリクエストを送信できるようにすることをおすすめします。 たとえば、login 関数の場合、user profiles 関数へのアクセスは許可しますが、search 関数へのアクセスは許可しません。

まず、呼び出し関数からのリクエストを受け入れるように受信側の関数を構成する必要があります。

  1. 受信側の関数で、Cloud Functions 起動元(roles/cloudfunctions.invoker)役割に呼び出し側の関数 ID を付与します。デフォルトでは、この ID は PROJECT_ID@appspot.gserviceaccount.com になります。

Console

  1. Google Cloud Console に移動します。

    Google Cloud Console に移動

  2. 権限を変更する関数の隣にあるチェックボックスをクリックします。

  3. 右上隅の [情報パネルを表示] をクリックして [権限] タブを開きます。ここに、関数に割り当てられた役割が表示されます。

  4. [メンバーを追加] フィールドに、呼び出し側関数のランタイム ID を入力します。これはサービス アカウントのメールアドレスです。

  5. [役割を選択] プルダウン メニューから Cloud Functions > Cloud Functions 起動元 役割を選択します。

  6. [保存] をクリックします。

gcloud

gcloud functions add-iam-policy-binding コマンドを使用します。

gcloud functions add-iam-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:CALLING_FUNCTION_IDENTITY' \
  --role='roles/cloudfunctions.invoker'

RECEIVING_FUNCTION は受信側の関数、CALLING_FUNCTION_IDENTITY は呼び出し側関数の ID です。

呼び出し側の関数で、以下の操作を行います。

  1. 対象(aud)に受信側関数の URL を設定して、Google によって署名された OAuth ID トークンを作成します。

  2. 関数に対するリクエストの Authorization: Bearer ID_TOKEN ヘッダーに ID トークンを含めます。

Node.js

const {get} = require('axios');

// TODO(developer): set these values
const REGION = 'us-central1';
const PROJECT_ID = 'my-project-id';
const RECEIVING_FUNCTION = 'myFunction';

// Constants for setting up metadata server request
// See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
const functionURL = `https://${REGION}-${PROJECT_ID}.cloudfunctions.net/${RECEIVING_FUNCTION}`;
const metadataServerURL =
  'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
const tokenUrl = metadataServerURL + functionURL;

exports.callingFunction = async (req, res) => {
  // Fetch the token
  const tokenResponse = await get(tokenUrl, {
    headers: {
      'Metadata-Flavor': 'Google',
    },
  });
  const token = tokenResponse.data;

  // Provide the token in the request to the receiving function
  try {
    const functionResponse = await get(functionURL, {
      headers: {Authorization: `bearer ${token}`},
    });
    res.status(200).send(functionResponse.data);
  } catch (err) {
    console.error(err);
    res.status(500).send('An error occurred! See logs for more details.');
  }
};

Python

import requests

# TODO<developer>: set these values
REGION = 'us-central1'
PROJECT_ID = 'my-project'
RECEIVING_FUNCTION = 'my-function'

# Constants for setting up metadata server request
# See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
function_url = f'https://{REGION}-{PROJECT_ID}.cloudfunctions.net/{RECEIVING_FUNCTION}'
metadata_server_url = \
    'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience='
token_full_url = metadata_server_url + function_url
token_headers = {'Metadata-Flavor': 'Google'}

def calling_function(request):
    # Fetch the token
    token_response = requests.get(token_full_url, headers=token_headers)
    jwt = token_response.text

    # Provide the token in the request to the receiving function
    function_headers = {'Authorization': f'bearer {jwt}'}
    function_response = requests.get(function_url, headers=function_headers)

    return function_response.text

Go


import (
	"context"
	"fmt"
	"io"

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

// makeGetRequest makes a request to the provided targetURL with an authenticated client.
func makeGetRequest(w io.Writer, targetURL string) error {
	// functionURL := "https://TARGET_URL"
	ctx := context.Background()

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

	resp, err := client.Get(targetURL)
	if err != nil {
		return fmt.Errorf("client.Get: %v", err)
	}
	defer resp.Body.Close()
	if _, err := io.Copy(w, resp.Body); err != nil {
		return fmt.Errorf("io.Copy: %v", err)
	}

	return nil
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.BufferedWriter;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.time.Duration;

public class BearerTokenHttp implements HttpFunction {

  // TODO<developer> specify values for these environment variables
  private static String REGION = System.getenv("TARGET_REGION");
  private static String PROJECT_ID = System.getenv("GCP_PROJECT");
  private static String RECEIVING_FUNCTION_NAME = "myFunction";

  private static String receivingFunctionUrl = String.format(
      "https://%s-%s.cloudfunctions.net/%s", REGION, PROJECT_ID, RECEIVING_FUNCTION_NAME);
  private static String metadataTokenEndpoint =
      "http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=";

  private static HttpClient client =
      HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException, InterruptedException {

    // Set up metadata server request
    // See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
    java.net.http.HttpRequest tokenRequest = java.net.http.HttpRequest.newBuilder()
        .uri(URI.create(metadataTokenEndpoint + receivingFunctionUrl))
        .GET()
        .header("Metadata-Flavor", "Google")
        .build();

    // Fetch the bearer token
    java.net.http.HttpResponse<String> tokenReponse =
        client.send(tokenRequest, java.net.http.HttpResponse.BodyHandlers.ofString());
    String token = tokenReponse.body();

    // Pass the token along to receiving function
    java.net.http.HttpRequest functionRequest = java.net.http.HttpRequest.newBuilder()
        .uri(URI.create(receivingFunctionUrl))
        .GET()
        .header("Authorization", "Bearer " + token)
        .build();
    java.net.http.HttpResponse<String> functionResponse =
        client.send(functionRequest, java.net.http.HttpResponse.BodyHandlers.ofString());

    // Write the results to the output:
    BufferedWriter writer = response.getWriter();
    writer.write(functionResponse.body());
  }
}

サービスと関数間のアクセス

コンピューティング メタデータにアクセス権のないコンピューティング インスタンス(独自のサーバーなど)から関数を呼び出す場合は、適切なトークンを手動で生成する必要があります。

  1. target_audience クレームを受信側の関数の URL に設定して、サービス アカウントの JWT に自己署名します。

  2. Google によって署名された ID トークンと自己署名された JWT を交換します。このトークンでは、aud クレームに上の URL が設定されています。

  3. 関数に対するリクエストの Authorization: Bearer ID_TOKEN ヘッダーに ID トークンを含めます。

Cloud IAP のドキュメントには、この機能を説明するサンプルコードがあります。

エンドユーザー

ほとんどのアプリケーションはエンドユーザーからのリクエストを処理します。許可されたエンドユーザーのみにアクセスを許可することをおすすめします。これを実現するには、Google ログインを統合して、ユーザーに Cloud Functions 起動元 IAM ロール(roles/cloudfunctions.invoker)を付与するか、Firebase Authentication を実装して認証情報を手動で検証します。

Google ログイン

まず、プロジェクトで Google ログインを有効にします。

  1. 保護する関数と同じプロジェクトで、アプリに OAuth 2.0 クライアント ID を作成します。
    1. [認証情報] ページに移動します。

      [認証情報] ページに移動

    2. 保護する関数のあるプロジェクトを選択します。
    3. [認証情報を作成] をクリックし、[OAuth クライアント ID] を選択します。
      1. クライアント ID を作成する前に、OAuth 同意画面の構成が必要になることがあります。必要な場合は、画面を構成して続行します。
    4. 認証情報を作成する [アプリケーションの種類] を選択します。
    5. 必要に応じて [名前] と [制限事項] を追加し、[作成] をクリックします。
  2. 保護する関数を再度デプロイします。これにより、関数に正しいクライアント ID が設定されます。

複数の OAuth クライアント ID がある場合(たとえば、Android、iOS、ウェブにそれぞれ ID がある場合)、関数が変更を取得できるように、それぞれの ID を追加した後に関数を再デプロイする必要があります。同様に、クライアント ID を削除する場合は、関数を再デプロイして、そのクライアント ID を削除してリクエストを拒否する必要があります。プロジェクト内のすべてのクライアント ID が受け入れられます。

ウェブアプリまたはモバイルアプリで、次の操作を行います。

  1. OAuth クライアント ID の ID トークンを取得します。
  2. 関数に対するリクエストの Authorization: Bearer ID_TOKEN ヘッダーに ID トークンを含めます。

Cloud Functions は、関数を開始する前に認証トークンを検証し、リクエストを許可または拒否します。リクエストが拒否された場合、そのリクエストは課金されません。

ユーザー プロフィール情報の取得

ユーザーのプロフィール情報にアクセスする場合、Authorization ヘッダーからトークンを抽出し、JSON ウェブトークンをデコードして本文を抽出します。

ID トークンの本文には、次の情報が含まれている必要があります。

{
 // These six fields are included in all Google ID Tokens.
 "iss": "https://accounts.google.com",
 "sub": "110169484474386276334",
 "azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "iat": "1433978353",
 "exp": "1433981953",

 // These seven fields are only included when the user has granted the "profile"
 // and "email" OAuth scopes to the application.
 "email": "testuser@gmail.com",
 "email_verified": "true",
 "name" : "Test User",
 "picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
 "given_name": "Test",
 "family_name": "User",
 "locale": "en"
}

このトークンはすでに IAM によって検証されているため、検証の必要はありません。

トラブルシューティング

本来許可されるはずのユーザー リクエストが拒否された場合は、ユーザーに roles/cloudfunctions.invoker 役割または cloudfunctions.functions.invoke 権限が付与されていることを確認してください。詳細については、Cloud Functions IAM リファレンスをご覧ください。

ウェブアプリ、認証、CORS

Google ログインと Cloud Functions IAM で保護されたウェブアプリを作成するときに、クロスオリジン リソース シェアリング(CORS)の処理が必要になる場合があります。 CORS プリフライト リクエストは Authorization ヘッダーなしで送信されるため、すべての非公開 HTTP 関数で拒否されます。プリフライト リクエストが失敗するため、メイン リクエストも失敗します。

この問題を回避するには、ウェブアプリと関数を同じドメインにホストして、CORS のプリフライト リクエストを回避します。それ以外の場合は、関数を公開し、関数コードで CORS と認証を処理する必要があります。

また、Cloud Endpoints プロキシをデプロイし、CORS を有効にすることもできます。認証機能を使用する場合は、Google ID トークン検証を有効にすることもできます。これにより、同じ認証トークンが検証されます。

Firebase Authentication

メールアドレスとパスワード、電話番号、Facebook や GitHub などのソーシャル プロバイダ、カスタム認証メカニズムを使用してユーザーを認証する場合は、Firebase Authentication を使用できます。

まず、プロジェクトと関数で Firebase Authentication を設定します。

  1. Firebase コンソールで Firebase Authentication を設定します。

    Firebase コンソールに移動

  2. 適切な Firebase Admin SDK をインポートして構成します。

  3. Firebase ID トークンを検証するコードをミドルウェアに追加します。

  4. 関数をデプロイして公開します。

ウェブアプリまたはモバイルアプリで、次の操作を行います。

  1. 適切な Firebase Authentication クライアント ライブラリを使用して、ID トークンを取得します。
  2. 関数に対するリクエストの Authorization: Bearer ID_TOKEN ヘッダーに ID トークンを含めます。

また、Cloud Endpoints プロキシをデプロイして Firebase ID トークンの検証を有効にすることもできます。これにより、同じ認証トークンが検証されます。

ユーザー プロフィール情報の取得

ユーザー プロフィール情報にアクセスするには、Firebase Admin SDK を使用してユーザーデータを取得します。

API キー

API キーは、API を呼び出すクライアント アプリケーションと GCP プロジェクトを識別します。これにより、割り当てチェックやレート制限と同様に、認証を簡単に行うことができます。

API キーは、ここで説明した他の認証方法ほど安全ではありません。理由は次のとおりです。

  1. API キーは長期間使用されます。キーが漏えいした場合、無制限に使用される可能性があります。少なくともキー ローテーションが発生するまでは再利用が可能です。ローテーションが発生すると、すべてのクライアントでキーの更新が必要になります。
  2. API キーはクライアント側に格納されています。悪意のあるユーザーが再利用する可能性があります。

割り当てとレート制限に API キーを使用する場合は、認証トークンと一緒に使用することをおすすめします。

API キーのアクセス権を構成するには、Cloud Endpoints プロキシをデプロイして、securityDefinitions を構成して API キーの検証を有効にします

次のステップ

認証する関数へのアクセス管理の方法を学習する