App Auth Guide

How Do You Use Managed App Auth in a Cloudflare Worker?

One secret, a table of connection IDs, and fetch through a proxy: OAuth-free API calls at the edge.

App AuthCloudflare WorkersOAuth

By Troy Tessalone · · 10 minutes

App Auth Guide

A practical field guide from Automation Ace.

Short answer: to use managed app auth in a Cloudflare Worker, store your platform's secret key (for example Nango or Pipedream Connect) as a Worker secret, keep a D1 or KV table that maps your users to their connection IDs, create short-lived connect sessions from a Worker route so users can connect their apps, save the connection ID from the platform's auth webhook, and call third-party APIs with plain fetch through the platform's authenticated proxy. The Worker never stores or refreshes OAuth tokens, which suits a stateless edge runtime.

Platform endpoints and Worker features change; check Cloudflare Workers docs and your platform's docs before shipping.

Short answer

  1. wrangler secret put the platform's secret key.
  2. Create a D1 table: user ID, integration, connection ID, status.
  3. Add a route that creates a connect session for the signed-in user.
  4. Add a webhook route that verifies and saves new connection IDs.
  5. Proxy API calls with fetch plus the connection ID.
  6. Flag connections that fail auth and offer reconnect.

Why managed auth suits Workers

Workers are short-lived and globally distributed. Building OAuth yourself would mean storing refresh tokens, preventing two locations from refreshing the same token at once, and encrypting secrets at rest. With managed auth, the Worker only holds one secret key and a list of connection IDs, and the platform does the stateful part. For Worker basics, see receiving webhooks with Cloudflare Workers and serverless functions compared.

Worker-specific considerations

Worker considerationWhat to do
RuntimeWorkers run fetch-based JavaScript, not full Node.js. Plain fetch to the platform's REST API always works; some Node SDKs need the nodejs_compat flag or may not run at all
SecretsUse Worker secrets (wrangler secret put) and read them from env; never ship them to the browser
StateStore user-to-connection mappings in D1 or KV; store no tokens
Auth for your own usersVerify your session cookie, JWT, or Cloudflare Access identity before using a connection
Scheduled workCron Triggers can run syncs or checks for connections that need attention
LimitsKeep requests short; stream proxy responses back instead of buffering large bodies

Step 1: Store the secret key

# Store the managed auth platform's secret key as a Worker secret
npx wrangler secret put NANGO_SECRET_KEY

# For local development, put it in .dev.vars (and keep that file out of git)
NANGO_SECRET_KEY=...

Step 2: Map users to connection IDs

-- D1: map your users to their connection IDs (no tokens stored here)
CREATE TABLE app_connections (
  user_id       TEXT NOT NULL,
  integration   TEXT NOT NULL,     -- e.g. "hubspot", "slack"
  connection_id TEXT NOT NULL,     -- from the platform's auth webhook
  status        TEXT DEFAULT 'ok', -- ok | needs_reconnect
  updated_at    TEXT,
  PRIMARY KEY (user_id, integration)
);

Bind the database to the Worker as DB in your Wrangler config. The table holds IDs only; the tokens stay with the platform.

Step 3: Create connect sessions and call the proxy

// src/index.js
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const user = await getUser(request, env);           // YOUR auth: session, JWT, Access
    if (!user) return new Response("Unauthorized", { status: 401 });

    // 1) Start a connection: return a short-lived connect session token
    if (url.pathname === "/connect/hubspot" && request.method === "POST") {
      const r = await fetch("https://api.nango.dev/connect/sessions", {
        method: "POST",
        headers: { Authorization: `Bearer ${env.NANGO_SECRET_KEY}`,
                   "Content-Type": "application/json" },
        body: JSON.stringify({
          tags: { end_user_id: user.id, organization_id: user.orgId },
          allowed_integrations: ["hubspot"],
        }),
      });
      const { data } = await r.json();
      return Response.json({ sessionToken: data.token });
    }

    // 2) Use the connection: call HubSpot through the proxy
    if (url.pathname === "/api/contacts") {
      const connectionId = await env.DB
        .prepare("SELECT connection_id FROM app_connections WHERE user_id = ? AND integration = 'hubspot'")
        .bind(user.id).first("connection_id");
      if (!connectionId) return Response.json({ error: "not_connected" }, { status: 409 });

      const res = await fetch("https://api.nango.dev/proxy/crm/v3/objects/contacts?limit=10", {
        headers: { Authorization: `Bearer ${env.NANGO_SECRET_KEY}`,
                   "Provider-Config-Key": "hubspot",
                   "Connection-Id": connectionId },
      });
      return new Response(res.body, { status: res.status,
        headers: { "content-type": res.headers.get("content-type") ?? "application/json" } });
    }

    return new Response("Not found", { status: 404 });
  },
};

Your frontend calls /connect/hubspot, then opens the platform's connect UI with the returned session token. Afterwards, /api/contacts returns HubSpot data without the Worker ever seeing a HubSpot token.

Step 4: Save connection IDs from the webhook

// 3) Receive the platform's auth webhook and save the connection ID
if (url.pathname === "/webhooks/nango" && request.method === "POST") {
  const raw = await request.text();
  if (!(await verifyNangoSignature(raw, request.headers, env))) {  // per Nango's docs
    return new Response("Invalid signature", { status: 401 });
  }
  const evt = JSON.parse(raw);
  if (evt.type === "auth" && evt.success) {
    await env.DB.prepare(
      "INSERT INTO app_connections (user_id, integration, connection_id, status, updated_at) " +
      "VALUES (?, ?, ?, 'ok', datetime('now')) " +
      "ON CONFLICT(user_id, integration) DO UPDATE SET connection_id = excluded.connection_id, " +
      "status = 'ok', updated_at = excluded.updated_at"
    ).bind(evt.tags.end_user_id, evt.providerConfigKey ?? "hubspot", evt.connectionId).run();
  }
  return new Response("ok");
}

Verify the webhook signature exactly as your platform documents before trusting it, and read the body as text first so the signature check sees the original bytes. More on webhook handling: what a webhook is.

Step 5: Handle reconnects and errors

  • Auth failures: when the proxy reports invalid credentials, set status = 'needs_reconnect' and show a Reconnect button that opens a reconnect session.
  • Rate limits: pass 429s back to the client with Retry-After, or retry with backoff in the Worker; see 429 errors.
  • Background checks: a Cron Trigger can check connections daily; see Cloudflare Cron Triggers.

Other platforms from a Worker

PlatformFrom a Worker
NangoREST via fetch: connect sessions, proxy with Provider-Config-Key and Connection-Id headers
Pipedream ConnectREST via fetch: get an API access token with your OAuth client credentials, then call the Connect API proxy with the external user ID and account ID
ComposioREST API or an SDK that supports the Workers runtime; tools and connected accounts are scoped by your user ID
ArcadeREST API or SDK to run tools for a user ID and return authorization links when needed
Zapier MCPCall it as a remote MCP server for your own account's actions, not for end-user connections

Compare them in the best managed app auth platforms. The same pattern works in any JavaScript backend; see calling third-party APIs from JavaScript without managing OAuth tokens.

Security checklist

  • Authenticate your own user before looking up their connection; never accept a connection ID from the browser.
  • Restrict which API paths the Worker will proxy, rather than forwarding any path the client sends.
  • Use scoped platform API keys where offered (for example, a key that can only create connect sessions for the connect route).
  • Verify webhook signatures; reject unsigned events.
  • Log request IDs and status codes, not tokens or full response bodies.

Building an agent on Workers? See how an agent can use a user's apps without seeing their tokens and how to limit which app actions an agent can take.

Frequently asked questions

Can I use Nango or Pipedream Connect in a Cloudflare Worker?

Yes. Call their REST APIs with fetch: create connect sessions, receive auth webhooks, and send API requests through the authenticated proxy with your secret key and the user’s connection ID. Store the secret key as a Worker secret.

Where should a Cloudflare Worker store OAuth tokens?

Ideally nowhere. With managed app auth, the platform stores and refreshes tokens, and the Worker stores only a mapping from your users to connection IDs in D1 or KV.

Do Node.js SDKs work in Cloudflare Workers?

Some do with the nodejs_compat flag, and some don’t. Plain fetch calls to the platform’s REST API always work and are often the simplest choice in a Worker.

How do I keep a Worker proxy from becoming an open relay?

Authenticate your own users first, look up their connection ID server-side, and allow only the specific API paths and methods your app needs instead of forwarding arbitrary requests.

How does a Worker know when a user connects an app?

The managed auth platform sends an auth webhook with the connection ID and the tags you set, such as your user ID. A Worker route verifies the signature and saves the connection ID.

App AuthCloudflare WorkersOAuth

Disclaimer: Managed app auth platforms, their SDKs, endpoints, plans, and features change quickly; details reflect public documentation and coverage at the time of writing, so confirm current details with each provider before building. Automation Ace is not affiliated with Composio, Nango, Pipedream, Arcade, or the other companies named here unless a link is marked as an affiliate link. This article may include links to apps, products, or services; some links may be affiliate links, which means Automation Ace may earn a commission at no extra cost to you.

Build Better Systems

Ready to automate with confidence?

Share your tools, process, and goals. Automation Ace can design the Zapier, AI, and automation setup that fits your business.

Start a Project →