Data Connect で Admin SDK を使用する

Firebase Admin SDK は、特権環境から Firebase を操作するために使用するサーバー ライブラリのセットです。たとえば、昇格された権限と権限借用された認証情報を使用して、Firebase Data Connect サービスに対してクエリとミューテーションを実行し、一括データ マネジメントなどのオペレーションを行うことができます。

Admin SDK は、読み取り/書き込みモードと読み取り専用モードの両方でオペレーションを呼び出す API を提供します。読み取り専用オペレーションを使用すると、データベース内のデータを変更できない管理機能を安心して実装できます。

Admin SDK の設定

サーバーで Firebase Data Connect の使用を開始するには、まず Node.js 用の Admin SDK をインストールして設定する必要があります。

スクリプトで Admin SDK を初期化する

SDK を初期化するには、Data Connect 拡張機能をインポートし、プロジェクト サービス ID とロケーションを宣言します。


import { initializeApp } from 'firebase-admin/app';
import { getDataConnect } from 'firebase-admin/data-connect';

// If you'd like to use OAuth2 flows and other credentials to log in,
// visit https://firebase.google.com/docs/admin/setup#initialize-sdk
// for alternative ways to initialize the SDK.

const app = initializeApp();

const dataConnect = getDataConnect({
    serviceId: 'serviceId',
    location: 'us-west2'
});

Admin SDK で使用するクエリとミューテーションを設計する

Admin SDK は、次の考慮事項を考慮して Data Connect オペレーションを実行する場合に役立ちます。

SDK と @auth(level: NO_ACCESS) オペレーション ディレクティブについて

Admin SDK は権限で動作するため、@auth ディレクティブを使用して設定されたアクセスレベル(NO_ACCESS レベルを含む)に関係なく、クエリとミューテーションを実行できます。

クライアント オペレーションと並行して、管理クエリとミューテーションを .gql ソースファイルに整理して管理スクリプトにインポートする場合は、認証アクセスレベルのない管理オペレーションをマークするか、より明示的に NO_ACCESS として設定することをおすすめします。いずれにしても、これにより、クライアントまたは他の非特権コンテキストからこのようなオペレーションが実行されるのを防ぐことができます。

Data Connect エミュレータで SDK を使用する

プロトタイプ環境とテスト環境では、ローカルデータに対してデータ シーディングなどのオペレーションを実行すると便利です。Admin SDK を使用すると、ローカルフローの認証と認可を無視できるため、ワークフローを簡素化できます。(ユーザーの権限借用を使用して、オペレーションの認証と認可の構成に準拠することを明示的に選択することもできます)。

環境変数 DATA_CONNECT_EMULATOR_HOST が設定されている場合、Firebase Admin SDK は Data Connect エミュレータに自動的に接続します。

export DATA_CONNECT_EMULATOR_HOST="127.0.0.1:9399"

詳しくは以下をご覧ください。

管理オペレーションを実行する

Admin SDK は、重要なデータに対する特権オペレーション用に提供されます。

Admin SDK には、次の 3 つの API セットが用意されています。

  • 生成された管理 SDK。クライアント SDK を生成するのと同じ方法で、gql 定義から生成されたタイプセーフ SDK です。
  • 任意の GraphQL オペレーションを実行するための一般的なインターフェース。コードでクエリとミューテーションを実装し、読み取り / 書き込み executeGraphql メソッドまたは読み取り専用 executeGraphqlRead メソッドに渡します。
  • 汎用の executeGraphql メソッドの代わりに、ミューテーション オペレーション専用のメソッド(insertinsertManyupsertupsertMany)を公開する、一括データ オペレーション用の特殊なインターフェース。

生成された SDK でデータを管理する

クライアント SDK を生成するのと同じ方法で、gql 定義から Admin SDK を生成します。

生成された Admin SDK には、gql 定義に対応するインターフェースと関数が含まれています。これらを使用して、データベースでオペレーションを実行できます。たとえば、曲のデータベースの SDK をクエリ getSongs とともに生成したとします。

import { initializeApp } from "firebase-admin/app";
import { getSongs } from "@dataconnect/admin-generated";

const adminApp = initializeApp();

const songs = await getSongs(
  { limit: 4 },
  { impersonate: { unauthenticated: true } }
);

または、コネクタ構成を指定します。

import { initializeApp } from "firebase-admin/app";
import { getDataConnect } from "firebase-admin/data-connect";
import {
  connectorConfig,
  getSongs,
} from "@dataconnect/admin-generated";

const adminApp = initializeApp();
const adminDc = getDataConnect(connectorConfig);

const songs = await getSongs(
  adminDc,
  { limit: 4 },
  { impersonate: { unauthenticated: true } }
);

認証されていないユーザーの権限借用

Admin SDK は信頼できる環境から実行されることを想定しているため、データベースへのアクセスは制限されません。

Admin SDK で公開オペレーションを実行する場合は、完全な管理者権限でオペレーションを実行しないようにする必要があります(最小権限の原則に従います)。代わりに、権限を借用したユーザー(次のセクションを参照)または権限を借用した認証されていないユーザーとしてオペレーションを実行する必要があります。認証されていないユーザーは、PUBLIC とマークされたオペレーションのみを実行できます。

上記の例では、getSongs クエリは未認証のユーザーとして実行されます。

ユーザーの権限借用

impersonate オプションで Firebase Authentication トークンの一部またはすべてを渡すことで、特定のユーザーに代わってオペレーションを実行することもできます。少なくとも、sub クレームでユーザーのユーザー ID を指定する必要があります。(これは、Data Connect GraphQL オペレーションで参照できる auth.uid サーバー値と同じ値です)。

ユーザーを偽装する場合、指定したユーザーデータが GraphQL 定義で指定された認証チェックに合格した場合にのみ、オペレーションは成功します。

生成された SDK を一般公開されているエンドポイントから呼び出す場合は、エンドポイントで認証を必須とし、認証トークンの完全性を検証してから、ユーザーの権限を借用することが重要です。

呼び出し可能な Cloud Functions を使用すると、認証トークンが自動的に検証され、次の例のように使用できます。

import { HttpsError, onCall } from "firebase-functions/https";

export const callableExample = onCall(async (req) => {
    const authClaims = req.auth?.token;
    if (!authClaims) {
        throw new HttpsError("unauthenticated", "Unauthorized");
    }

    const favoriteSongs = await getMyFavoriteSongs(
        undefined,
        { impersonate: { authClaims } }
    );

    // ...
});

それ以外の場合は、Admin SDKverifyIdToken メソッドを使用して、認証トークンを検証してデコードします。たとえば、エンドポイントがプレーンな HTTP 関数として実装され、標準どおりに authorization ヘッダーを使用して Firebase Authentication トークンをエンドポイントに渡したとします。

import { getAuth } from "firebase-admin/auth";
import { onRequest } from "firebase-functions/https";

const auth = getAuth();

export const httpExample = onRequest(async (req, res) => {
    const token = req.header("authorization")?.replace(/^bearer\s+/i, "");
    if (!token) {
        res.sendStatus(401);
        return;
    }
    let authClaims;
    try {
        authClaims = await auth.verifyIdToken(token);
    } catch {
        res.sendStatus(401);
        return;
    }

    const favoriteSongs = await getMyFavoriteSongs(
        undefined,
        { impersonate: { authClaims } }
    );

    // ...
});

安全で一般公開されていない環境からデータ移行などの真の管理タスクを実行する場合にのみ、検証可能なソースから取得されていないユーザー ID を指定する必要があります。

// Never do this if end users can initiate execution of the code!
const favoriteSongs = await getMyFavoriteSongs(
  undefined,
  { impersonate: { authClaims } }
);

無制限のアクセスで実行する

管理者レベルの権限を必要とするオペレーションを実行する場合は、呼び出しから impersonate パラメータを省略します。

await upsertSong(adminDc, {
  title: songTitle_one,
  instrumentsUsed: [Instrument.VOCAL],
});

この方法で呼び出されたオペレーションは、データベースに完全にアクセスできます。管理目的でのみ使用するクエリまたはミューテーションがある場合は、@auth(level: NO_ACCESS) ディレクティブで定義する必要があります。これにより、管理者レベルの呼び出し元のみがこれらのオペレーションを実行できるようになります。

executeGraphql メソッドを使用してデータを管理する

gql ミューテーションまたはクエリを定義していない 1 回限りのオペレーションを実行する必要がある場合は、executeGraphql メソッドまたは読み取り専用の executeGraphqlRead メソッドを使用できます。

認証されていないユーザーの権限借用

Admin SDK で公開オペレーションを実行する場合は、完全な管理者権限でオペレーションを実行しないようにする必要があります(最小権限の原則に従います)。代わりに、権限を借用したユーザー(次のセクションを参照)または権限を借用した認証されていないユーザーとしてオペレーションを実行する必要があります。認証されていないユーザーは、PUBLIC とマークされたオペレーションのみを実行できます。

// Query to get posts, with authentication level PUBLIC
const queryGetPostsImpersonation = `
    query getPosts @auth(level: PUBLIC) {
        posts {
          description
        }
    }`;

// Attempt to access data as an unauthenticated user
const optionsUnauthenticated: GraphqlOptions<undefined> = {
    impersonate: {
        unauthenticated: true
    }
};

// executeGraphql with impersonated unauthenticated user scope
const gqlResponse = await dataConnect.executeGraphql<UserData, undefined>(queryGetPostsImpersonation, optionsUnauthenticated);

ユーザーの権限借用

また、特定のユーザーの代わりに、制限付きの認証情報に基づいてユーザーデータを変更するスクリプトが必要になるユースケースもあります。このアプローチは、最小権限の原則に準拠しています。

このインターフェースを使用するには、Authentication トークン形式に準拠したカスタマイズされた JWT 認証トークンから情報を収集します。カスタム トークン ガイドもご覧ください。

// Get the current user's data
const queryGetUserImpersonation = `
    query getUser @auth(level: USER) {
        user(key: {uid_expr: "auth.uid"}) {
            id,
            name
        }
    }`;

// Impersonate a user with the specified auth claims
const optionsAuthenticated: GraphqlOptions<undefined> = {
    impersonate: {
        authClaims: {
            sub: 'QVBJcy5ndXJ1'
        }
    }
};

// executeGraphql with impersonated authenticated user scope
const gqlResponse = await dataConnect.executeGraphql<UserData, undefined>(queryGetUserImpersonation, optionsAuthenticated);

// gqlResponse -> { "data": { "user": { "id": "QVBJcy5ndXJ1", "name": "Fred" } } }

管理者の認証情報を使用する

管理者レベルの権限を必要とするオペレーションを実行する場合は、呼び出しから impersonate パラメータを省略します。

// User can be publicly accessible, or restricted to admins
const query = "query getProfile(id: AuthID) { user(id: $id) { id name } }";

interface UserData {
  user: {
    id: string;
    name: string;
  };
}

export interface UserVariables {
  id: string;
}

const options:GraphqlOptions<UserVariables> = { variables: { id: "QVBJcy5ndXJ1" } };

// executeGraphql
const gqlResponse = await dataConnect.executeGraphql<UserData, UserVariables>(query, options);

// executeGraphqlRead (similar to previous sample but only for read operations)
const gqlResponse = await dataConnect.executeGraphqlRead<UserData, UserVariables>(query, options);

// gqlResponse -> { "data": { "user": { "id": "QVBJcy5ndXJ1", "name": "Fred" } } }

この方法で呼び出されたオペレーションは、データベースに完全にアクセスできます。管理目的でのみ使用するクエリまたはミューテーションがある場合は、@auth(level: NO_ACCESS) ディレクティブを使用して定義する必要があります。これにより、管理者レベルの呼び出し元のみがこれらのオペレーションを実行できるようになります。

一括データ オペレーションを実行する

Firebase では、本番環境データベースでの一括データ オペレーションに Admin SDK を使用することをおすすめします。

SDK には、一括データを操作するための次のメソッドが用意されています。各メソッドは、指定された引数から GraphQL ミューテーションを構築して実行します。


// Methods of the bulk operations API
// dc is a Data Connect admin instance from getDataConnect

const resp = await dc.insert("movie" /*table name*/, data[0]);
const resp = await dc.insertMany("movie" /*table name*/, data);
const resp = await dc.upsert("movie" /*table name*/, data[0]);
const resp = await dc.upsertMany("movie" /*table name*/, data);

一括オペレーションのパフォーマンスに関する注意事項

バックエンドへのリクエストごとに Cloud SQL へのラウンド トリップが発生するため、バッチ処理の量が多いほどスループットが高くなります。

ただし、バッチサイズが大きいほど、生成される SQL ステートメントは長くなります。PostgreSQL SQL ステートメントの長さの上限に達すると、エラーが発生します。

実際には、ワークロードに適したバッチサイズをテストして見つけてください。

次のステップ