ScreenshotNeo

BlogHow-to

How to Pass a User’s Password to Puppeteer in a Firebase Callable Function

Learn when Firebase passwords belong in Auth, how callable auth works, and how to handle separate Puppeteer logins without leaking credentials.

By the ScreenshotNeo team30 September 202610 min read

How to Pass a User's Password to Puppeteer in a Firebase Callable Function

Short answer: do not pass a user’s Firebase password to Puppeteer so a callable function can identify the user. Sign in with Firebase Auth in the client, call the HTTPS callable with the Firebase Functions SDK, and use the callable request’s authentication context in the function. Firebase automatically includes the available Authentication token in callable requests, and the handler receives the caller in request.auth. See the Firebase callable guide and callable protocol reference.

A Firebase ID token proves the caller’s Firebase identity. It does not log Puppeteer into an unrelated website. If the browser must access another service, use that service’s supported API or delegated sign-in flow. Only submit a third-party password when it is explicitly required, authorized, and handled as a short-lived secret.

1. Understand the identity boundaries

There are usually two different accounts in this design:

  • Your application account: the user signs in with Firebase Auth. The password is consumed by the Firebase client SDK during signInWithEmailAndPassword.
  • The target website account: Puppeteer may need a browser session for a separate site. Firebase Auth does not create that site’s cookies, local storage, or session.

The callable payload and authentication context are separate. Values in request.data come from the application. request.auth is populated by Firebase when the request includes a valid Authentication token. A field such as request.data.password is therefore ordinary application input, not callable authentication.

  1. Enable the required Firebase Authentication provider.
  2. Sign the user in on the client with Firebase Auth.
  3. Call the HTTPS callable through the Firebase Functions SDK.
  4. Reject unauthenticated requests before starting Puppeteer.
  5. Authorize the requested operation for request.auth.uid.
  6. Use Firebase APIs or an authorized target-site session for the browser work.

The Firebase password-auth documentation shows the client-side password flow with signInWithEmailAndPassword(auth, email, password): Firebase password authentication.

Firebase identity reaches the callable as authentication context; a separate site session still needs its own authorized flow.
Firebase identity reaches the callable as authentication context; a separate site session still needs its own authorized flow.

Client: sign in, then call the function

import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
import { getFunctions, httpsCallable } from "firebase/functions";
import { app } from "./firebase.js";

const auth = getAuth(app);
const functions = getFunctions(app);

await signInWithEmailAndPassword(auth, email, password);

const runAutomation = httpsCallable(functions, "runAutomation");
const result = await runAutomation({
  pageUrl: "https://example.com/account"
});

console.log(result.data);

The password is used by the Auth SDK. It is not included in the callable data. The SDK attaches the user’s Firebase Authentication token when available.

Callable handler: check identity before Puppeteer

const { onCall, HttpsError } = require("firebase-functions/https");
const puppeteer = require("puppeteer");

exports.runAutomation = onCall(async (request) => {
  if (!request.auth) {
    throw new HttpsError(
      "unauthenticated",
      "Sign in before running this action."
    );
  }

  const uid = request.auth.uid;
  const { pageUrl } = request.data || {};

  if (typeof pageUrl !== "string" || !pageUrl.startsWith("https://")) {
    throw new HttpsError("invalid-argument", "A valid HTTPS pageUrl is required.");
  }

  // Apply application-specific authorization for uid here.
  const browser = await puppeteer.launch({
    headless: true,
    args: ["--no-sandbox", "--disable-setuid-sandbox"]
  });

  try {
    const page = await browser.newPage();
    await page.goto(pageUrl, { waitUntil: "networkidle2", timeout: 30000 });
    return { ok: true, uid, title: await page.title() };
  } finally {
    await browser.close();
  }
});

Authentication answers “who is calling?” Authorization still decides whether that UID may perform this operation. Firebase’s callable guide also recommends considering App Check to help protect callable endpoints from abuse.

3. When Puppeteer needs a separate website login

For a target site that is not Firebase, choose the least sensitive supported integration:

Requirement Preferred approach Why
Read or change Firebase data Firebase client/Admin APIs No browser password or scraping session is needed.
Access a partner service Official API or delegated authorization Tokens can be scoped and revoked more cleanly than passwords.
Automate an authorized web UI Target site’s documented login/session mechanism Preserves the site’s intended security boundary.
Reuse an existing browser session Short-lived, controlled cookies Avoids sending a reusable password through your function.

Never assume a Firebase ID token is accepted by another site’s login form. It identifies the user to Firebase only. If the target service supports an OAuth or service-account flow, use that instead of collecting a password.

Passing a third-party password only when required

The callable protocol permits JSON data, so a deliberately designed function could receive a credential. That does not make it a good default. If an authorized integration truly requires a password:

  • Require request.auth and authorize the caller first.
  • Validate the target host and the exact operation; do not accept arbitrary URLs.
  • Use HTTPS end to end and keep the value in memory for the shortest practical time.
  • Do not log the request, error object, headers, page content, screenshots, or returned data containing the secret.
  • Do not store or echo the password. Do not put it in a query string.
  • Close the browser and clear references in all success and failure paths.
  • Prefer a one-time exchange, API token, or session cookie supplied by the target service.
exports.loginToAuthorizedSite = onCall(async (request) => {
  if (!request.auth) {
    throw new HttpsError("unauthenticated", "Authentication required.");
  }

  const { username, sitePassword } = request.data || {};
  if (typeof username !== "string" || typeof sitePassword !== "string") {
    throw new HttpsError("invalid-argument", "Credentials are required.");
  }

  // Confirm that this UID is allowed to use this specific integration.
  // Never console.log sitePassword or include it in an error message.

  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.goto("https://authorized.example/login", {
      waitUntil: "domcontentloaded",
      timeout: 30000
    });
    await page.type("input[name=email]", username);
    await page.type("input[name=password]", sitePassword);
    await Promise.all([
      page.waitForNavigation({ waitUntil: "networkidle2" }),
      page.click("button[type=submit]")
    ]);
    return { ok: true };
  } catch (error) {
    throw new HttpsError("internal", "The authorized browser operation failed.");
  } finally {
    await browser.close();
  }
});

This example is intentionally scoped to an authorized site. Selectors, redirects, MFA, CAPTCHA, and terms of service are target-specific.

4. Cookies and browser sessions

If the site provides a supported session cookie, use a browser context rather than a password where possible. Puppeteer’s current API reference marks Page.setCookie() obsolete and recommends Browser.setCookie() or BrowserContext.setCookie(): Puppeteer cookie API.

const browser = await puppeteer.launch({ headless: true });
const context = await browser.createBrowserContext();
await context.setCookie({
  name: "session",
  value: sessionValue,
  domain: "authorized.example",
  path: "/",
  secure: true,
  httpOnly: true
});
const page = await context.newPage();
await page.goto("https://authorized.example/account", {
  waitUntil: "networkidle2",
  timeout: 30000
});

Keep cookie scope narrow: set the correct domain and path, use secure and HTTP-only attributes where supported, and discard the context after the job. Do not accept arbitrary cookie domains from the client.

5. Token verification outside callable functions

If another backend boundary receives a Firebase ID token directly, verify it with the Firebase Admin SDK. verifyIdToken() returns decoded claims including the UID. Firebase notes that revocation is not checked by default, so enable the appropriate revocation check for your threat model and authorization rules: Verify ID tokens using the Admin SDK.

const admin = require("firebase-admin");
admin.initializeApp();

async function requireUser(idToken) {
  return admin.auth().verifyIdToken(idToken);
}

// decoded.uid identifies the Firebase user; authorize it before Puppeteer work.

Do not manually copy an ID token into Puppeteer as though it were a website password. A token is useful only where the receiving service documents that token format and audience.

6. Custom tokens are not browser credentials

Firebase custom tokens are minted server-side and exchanged by the client with signInWithCustomToken(). They are an alternative Firebase sign-in mechanism, not a general cookie for arbitrary websites. Firebase documents that custom tokens expire after one hour and that service-account private keys must remain confidential: Create custom tokens.

7. Common errors and fixes

Symptom Likely cause Fix
request.auth is null The client is not signed in, the wrong Functions region is used, or the call bypasses the Firebase SDK. Wait for Firebase Auth state, call through httpsCallable, and confirm the deployed function name and region.
“Unauthenticated” after sign-in The Auth project and Functions project configuration do not match, or the token has expired. Check Firebase configuration, refresh the current user, and retry through the SDK.
Puppeteer receives a Firebase token but remains logged out The target website does not trust Firebase tokens. Use that site’s API, OAuth flow, session cookie, or documented UI login.
Login redirects forever Consent, MFA, bot checks, or an unexpected redirect domain. Follow the target site’s supported automation policy; add bounded waits and allow-listed redirect hosts.
Function times out Browser startup, navigation, or network activity exceeds the callable timeout. Set explicit navigation and selector timeouts, close every browser in finally, and move long work to an asynchronous job.
“Execution context was destroyed” A click caused navigation while another operation was still using the old page context. Coordinate the click and navigation with Promise.all and wait for the new page state.
Password appears in logs Request or error objects were logged wholesale. Remove credential logging, redact structured logs, rotate the exposed secret, and review retention.
Cookie does not work Wrong domain, path, secure flag, or expired value. Use BrowserContext.setCookie with the exact host and a live session value.

8. Reliability, performance, and cost

Browser lifecycle

Launching Chromium is expensive compared with a normal HTTP request. Reuse a browser process only when isolation requirements permit it, and create a fresh context per user or job. Always close pages, contexts, and browsers in finally blocks. Limit concurrent jobs so memory pressure does not terminate the function.

Timeouts and retries

Use separate limits for browser launch, navigation, selectors, and the callable itself. Retry only transient failures such as a network reset. Do not blindly retry a login submission: it can trigger account lockouts or duplicate side effects. Include an idempotency key for operations that change data.

Cold starts and deployment

Puppeteer needs a compatible Chromium binary and enough memory. Confirm that your deployment package includes the browser dependency and that the runtime supports the Node.js version you use. Keep the callable response small; return a job ID when capture or automation may exceed the request deadline.

Cost control

Each browser invocation consumes function time and resources. Reject unauthenticated calls before launching Chromium, validate URLs against an allow-list, cap page counts, and avoid loading unnecessary assets. If the operation only needs Firebase data, calling Firebase directly is cheaper and more reliable than opening a browser.

9. Or skip the browser setup

If your actual goal is to obtain a clean screenshot of a URL, ScreenshotNeo provides a single GET request instead of maintaining Puppeteer, Chromium, login flows, and cleanup code. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. You can turn each step off. Bot checks, CAPTCHA pages, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies the result with X-Page-Verdict and X-Billed headers.

A capture service can remove common overlays before returning the screenshot.
A capture service can remove common overlays before returning the screenshot.

See the ScreenshotNeo API documentation for all options. The same endpoint supports PNG, JPEG, WebP, or PDF output.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const bytes = await res.arrayBuffer();
await require('node:fs').promises.writeFile('shot.webp', Buffer.from(bytes));

ScreenshotNeo also supports full-page captures with lazy images loaded, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF paper settings and page ranges, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, Authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which can simplify migration.

An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Plans include 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

10. Security checklist

  • Use Firebase Auth for Firebase identity; do not resend its password to the callable.
  • Check request.auth and authorize the UID before browser work.
  • Keep target-site credentials separate from Firebase credentials.
  • Prefer APIs, OAuth, or short-lived cookies over passwords.
  • Never log, persist, echo, screenshot, or return secrets.
  • Allow-list target hosts and validate every URL, selector, and operation.
  • Use bounded timeouts, concurrency limits, and cleanup in finally.
  • Rotate any credential exposed through logs, traces, errors, or screenshots.

FAQ

Can I put the Firebase password in request.data?

The protocol can carry arbitrary JSON, but the callable does not need the Firebase password to identify the caller. Sign in with Firebase Auth and use request.auth instead.

Does a callable automatically include an ID token?

When available, the Firebase client SDK automatically includes Authentication tokens in callable requests. The handler still must check authentication and authorization.

Can Puppeteer use a Firebase ID token as a website login?

Only if that website explicitly documents Firebase token support. Otherwise, use the site’s own API, delegated authorization, cookie session, or login flow.

Should I verify a token inside an onCall handler?

The callable infrastructure supplies the authentication context. Independently verify tokens at other backend boundaries with the Admin SDK when you receive a raw ID token.

What if the target site requires MFA?

Do not attempt to bypass it. Use the site’s supported integration, an approved service account, or a user-mediated session handoff.