Use the Admin SDK with Data Connect

The Firebase Admin SDK is a set of server libraries that lets you interact with Firebase from privileged environments to perform actions like performing queries and mutations on a Firebase Data Connect service for bulk data management and other operations with elevated privileges and impersonated credentials.

The Admin SDK provides you with an API to call operations in both read/write and read-only modes. With the read-only operations, you have the peace of mind of implementing administrative functions that cannot modify data in your databases.

Admin SDK Setup

To get started using the with Firebase Data Connect on your server, you'll first need to install and set up the Admin SDK for Node.js.

Initialize the Admin SDK in your scripts

To initialize the SDK, import the Data Connect extensions and declare your project service ID and location.


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'
});

Design queries and mutations to use with the Admin SDK

The Admin SDK is useful for running Data Connect operations, given the following considerations.

Understand the SDK and @auth(level: NO_ACCESS) operation directive

Since the Admin SDK operates with privileges, it can execute any of your queries and mutations regardless of access levels set using @auth directives, including the NO_ACCESS level.

If alongside your client operations, you organize your administrative queries and mutations in .gql source files for import into administrative scripts, Firebase recommends that you mark the administrative operations without any authorization access level, or perhaps be more explicit and set them as NO_ACCESS. Either way, this prevents such operations from being executed from clients or in other non-privileged contexts.

Use the SDK with the Data Connect emulator

In prototype and test environments, it can be useful to perform data seeding and other operations on local data. The Admin SDK lets you simplify your workflows since it can ignore authentication and authorization for local flows. (You can also explicitly opt in to complying with your operations' authentication and authorization configuration with user impersonation.)

The Firebase Admin SDKs automatically connects to the Data Connect emulator when the DATA_CONNECT_EMULATOR_HOST environment variable is set:

export DATA_CONNECT_EMULATOR_HOST="127.0.0.1:9399"

For more information, see:

Run admin operations

The Admin SDK is provided for privileged operations on your critical data.

The Admin SDK provides three sets of APIs:

  • Generated admin SDKs, which are type-safe SDKs generated from your gql definitions in the same way that you generate client SDKs.
  • A general interface for running arbitrary GraphQL operations, in which your code implements queries and mutations and passes them to the read-write executeGraphql method or the read-only executeGraphqlRead method.
  • A specialized interface for bulk data operations, which instead of generic executeGraphql methods, exposes dedicated methods for mutation operations: insert, insertMany, upsert, and upsertMany.

Manage data with generated SDKs

You generate admin SDKs from your gql definitions in the same way that you generate client SDKs.

The generated admin SDK contains interfaces and functions that correspond with your gql definitions, which you can use to perform operations on your database. For example, suppose you generated an SDK for a database of songs, along with a query, 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 } }
);

Or, to specify a connector configuration:

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 } }
);

Impersonating an unauthenticated user

Admin SDKs are intended to be run from trusted environments, and therefore have unrestricted access to your databases.

When you run public operations with the admin SDK, you should avoid running the operation with full administrator privileges (following the principle of least privilege). Instead, you should run the operation either as an impersonated user (see the next section), or as an impersonated unauthenticated user. Unauthenticated users can only run operations marked as PUBLIC.

In the example above, the getSongs query is executed as an unauthenticated user.

Impersonating a user

You can also perform operations on behalf of specific users by passing part or all of a Firebase Authentication token in the impersonate option; at a minimum, you must specify the user's user ID in the sub claim. (This is the same value as the auth.uid server value you can reference in Data Connect GraphQL operations.)

When you impersonate a user, the operation will succeed only if the user data you provided passes the authentication checks specified in your GraphQL definition.

If you're calling the generated SDK from a publicly accessible endpoint, it is crucial that the endpoint require authentication and that you validate the integrity of the authentication token before you use it to impersonate a user.

When using callable Cloud Functions, the authentication token is automatically verified and you can use it as in the following example:

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 } }
    );

    // ...
});

Otherwise, use the Admin SDK's verifyIdToken method to validate and decode the authentication token. For example, suppose your endpoint is implemented as a plain HTTP function and you have passed the Firebase Authentication token to your endpoint using the authorization header, as is standard:

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 } }
    );

    // ...
});

Only when performing true administrative tasks, such as data migration, from a secure, non-publicly-accessible environment, should you specify a user ID that did not originate from a verifiable source:

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

Running with unrestricted access

If you're performing an operation that requires admin level permissions, omit the impersonate parameter from the call:

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

An operation called in this manner has complete access to the database. If you have queries or mutations intended only to be used for administration purposes, you should define them with the @auth(level: NO_ACCESS) directive. Doing so ensures that only admin-level callers can execute these operations.

Manage data with executeGraphql methods

If you need to execute one-off operations for which you have not defined gql mutations or queries, you can use the executeGraphql method or the read-only executeGraphqlRead method.

Impersonating an unauthenticated user

When you run public operations with the admin SDK, you should avoid running the operation with full administrator privileges (following the principle of least privilege). Instead, you should run the operation either as an impersonated user (see the next section), or as an impersonated unauthenticated user. Unauthenticated users can only run operations marked as 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);

Impersonating a user

There are also use cases where you want your scripts to modify user data based on limited credentials, on behalf of a specific user. This approach honors the principle of least privilege.

To use this interface, gather information from a customized JWT auth token that follows the Authentication token format. Also see the custom tokens guide.

// 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" } } }

Use administrative credentials

If you're performing an operation that requires admin level permissions, omit the impersonate parameter from the call:

// 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" } } }

An operation called in this manner has complete access to the database. If you have queries or mutations intended only to be used for administration purposes, you should define them with the @auth(level: NO_ACCESS) directive. Doing so ensures that only admin-level callers can execute these operations.

Perform bulk data operations

Firebase recommends you use the Admin SDK for bulk data operations on production databases.

The SDK provides the following methods for working with bulk data. From the provided arguments, each method constructs and executes a GraphQL mutation.


// 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);

Performance notes for bulk operations

Each request to the backend will incur one round trip to Cloud SQL, so the more you batch, the higher the throughput is.

However, the larger the batch size, the longer the generated SQL statement is. When the PostgreSQL SQL statement length limit is reached, you will encounter an error.

In practice, experiment to find the appropriate batch size for your workload.

What's next?