# Configure gatekeeper credentials

> Register a third-party OAuth app and wire its credentials into a connector. Covers the `${PUBLIC_BASE_URL}/gatekeeper/<name>/oauth` redirect-URI contract, the per-connector `CLIENT_ID`/`CLIENT_SECRET` secrets, `deploy-inputs.json` overrides with `setupSteps` and `consoleUrl`, dev seeding from shell variables, and connectors that need no OAuth app.

- Repository: cloudflare/cloudflare-os
- GitHub: https://github.com/cloudflare/cloudflare-os
- Human docs: https://grok-wiki.com/public/docs/cloudflare-cloudflare-os-838773bb92dd
- Complete Markdown: https://grok-wiki.com/public/docs/cloudflare-cloudflare-os-838773bb92dd/llms-full.txt

## Source Files

- `packages/gatekeeper-github/README.md`
- `packages/gatekeeper-github/deploy-inputs.json`
- `packages/gatekeeper-google/README.md`
- `packages/gatekeeper-email/README.md`
- `packages/gatekeeper-mcp/README.md`
- `run-dev-server.js`
- `docs/public-server.md`

---

---
title: "Configure gatekeeper credentials"
description: "Register a third-party OAuth app and wire its credentials into a connector. Covers the `${PUBLIC_BASE_URL}/gatekeeper/<name>/oauth` redirect-URI contract, the per-connector `CLIENT_ID`/`CLIENT_SECRET` secrets, `deploy-inputs.json` overrides with `setupSteps` and `consoleUrl`, dev seeding from shell variables, and connectors that need no OAuth app."
---

Gatekeeper connectors that talk to a third-party OAuth provider read two secrets from their own worker environment: `CLIENT_ID` and `CLIENT_SECRET`. Each connector is served under `/gatekeeper/<name>/`, where `<name>` is the package suffix lowercased (`packages/gatekeeper-github` → `/gatekeeper/github`), and the OAuth callback the connector sends is always `<base>/gatekeeper/<name>/oauth`. Locally that base is `http://localhost:8787`; in a deployment it is the instance's `PUBLIC_BASE_URL`. A connector whose `CLIENT_ID` or `CLIENT_SECRET` is missing serves a "Not configured" page instead of starting the authorization flow.

## The redirect-URI contract

```text
provider console                 this deployment
┌──────────────────────┐         ┌──────────────────────────────────────────┐
│ OAuth app            │         │ router                                   │
│  callback URL   ─────┼────────►│  /gatekeeper/<name>/oauth                │
│  client id           │         │        │                                 │
│  client secret       │         │        ▼                                 │
└──────────────────────┘         │  packages/gatekeeper-<name>              │
        ▲                        │    env.CLIENT_ID / env.CLIENT_SECRET     │
        └── copied into ─────────┤    (.env locally, secrets when deployed) │
                                 └──────────────────────────────────────────┘
```

| Environment | Redirect URI to register |
| --- | --- |
| Local dev | `http://localhost:8787/gatekeeper/<name>/oauth` |
| Deployed | `${PUBLIC_BASE_URL}/gatekeeper/<name>/oauth` |

<Warning>
The redirect URI must match byte for byte. GitHub returns `redirect_uri_mismatch` when the registered callback differs from what the connector sends — check for a trailing slash, and for `http` versus `https` on localhost.
</Warning>

## Register the OAuth app

<Tabs>
<Tab title="GitHub">

Use a GitHub **OAuth App**, not a GitHub App. Only OAuth Apps honor the OAuth `scope` parameter, which is what lets sign-in request minimal scopes (`read:user user:email`) while a connection requests the full set (`repo read:user user:email`). A GitHub App (client id beginning `Iv…`) ignores `scope` and fails the sign-in email lookup with `Resource not accessible by integration` unless you separately grant it the **Email addresses** account permission.

<Steps>
<Step title="Create the app">
Go to GitHub **Settings → Developer settings → OAuth Apps** (`https://github.com/settings/developers`) and click **New OAuth App**.

- **Application name**: anything, e.g. `Gadgets Local Dev`
- **Homepage URL**: `http://localhost:3000`
- **Authorization callback URL**: `http://localhost:8787/gatekeeper/github/oauth`

Click **Register application**.
</Step>
<Step title="Generate a client secret">
On the app's settings page, click **Generate a new client secret** and copy both the **Client ID** and the secret.
</Step>
<Step title="Write the credentials">
Create `packages/gatekeeper-github/.env`:

```bash title="packages/gatekeeper-github/.env"
CLIENT_ID=your-client-id-here
CLIENT_SECRET=your-client-secret-here
```

The `.env` file is gitignored and must never be committed.
</Step>
</Steps>

</Tab>
<Tab title="Google">

A single Google OAuth client covers both sign-in and connections. Sign-in requests only `openid`, `userinfo.email`, and `userinfo.profile`; connecting a resource additionally requests the scopes for that resource type only (Gmail, Docs, Sheets, Calendar, or BigQuery).

<Steps>
<Step title="Create a Google Cloud project">
In the [Google Cloud Console](https://console.cloud.google.com/), open the project dropdown, click **New Project**, name it, click **Create**, then select it.
</Step>
<Step title="Enable the APIs you need">
Under **APIs & Services → Library**, enable the APIs for the resource types you plan to connect: Gmail API, Google Docs API, Google Drive API, Google Sheets API, Google Calendar API, BigQuery API.

The Drive API is used only so the resource pickers can search Docs and Sheets by title; document reads and edits go through the Docs API and spreadsheet reads through the Sheets API.
</Step>
<Step title="Configure the consent screen">
Under **APIs & Services → OAuth consent screen**, choose **External**, fill in an app name, and save. The Scopes page can be left empty — scopes come from the OAuth request itself, not from the console configuration.
</Step>
<Step title="Add yourself as a test user">
While the app is in Testing mode, only listed test users can complete OAuth. Add the Google address you will sign in with under **Add Users**, then save.
</Step>
<Step title="Create the OAuth client">
Under **APIs & Services → Credentials**, click **Create Credentials → OAuth client ID**, application type **Web application**. Under **Authorized redirect URIs** add `http://localhost:8787/gatekeeper/google/oauth`, then click **Create**. Copy the **Client ID** and **Client Secret** from the popup.
</Step>
<Step title="Write the credentials">
Create `packages/gatekeeper-google/.env`:

```bash title="packages/gatekeeper-google/.env"
CLIENT_ID=your-client-id-here.apps.googleusercontent.com
CLIENT_SECRET=your-client-secret-here
```
</Step>
</Steps>

</Tab>
</Tabs>

### Google scopes requested per surface

| Scope | Requested for |
| --- | --- |
| `openid`, `userinfo.profile`, `userinfo.email` | Identity — sign-in, and always included on connect |
| `gmail.modify` | Gmail thread reads, organization, replies, forwards, sending (includes labels and send) |
| `documents` | Google Docs reads and edits |
| `drive.metadata.readonly` | Resource pickers searching Docs and Sheets by title |
| `spreadsheets.readonly` | Spreadsheet metadata and cell values |
| `calendar.calendarlist.readonly` | Listing calendars in the resource picker |
| `calendar.events` | Managing the selected calendar and checking availability |
| `bigquery` | BigQuery dry-runs and queries — broader than `bigquery.readonly` because dry-runs use `jobs.insert`; the gatekeeper enforces read-only SQL and resource-scope checks before running a query |

## Optional: enable the connector for sign-in

Adding a connector's name to the `AUTH_GATEKEEPERS` allowlist puts a "Continue with …" button on the login page. Set it in the repo-root `.dev.vars` for local development:

```ini title=".dev.vars"
AUTH_GATEKEEPERS=cloudflare,google,github
```

Users are keyed by the provider's verified email — GitHub's primary verified email, Google's `email_verified` address. The sign-in grant is transient and discarded right after the email is read. No extra provider configuration is needed for a GitHub OAuth App or a Google client; a Google app still in Testing mode requires the signing-in user to be a listed test user.

## Seeding credentials in dev from shared shell variables

`run-dev-server.js` generates a `wrangler.dev.jsonc` per gatekeeper and injects OAuth credentials that are shared with the sign-in flow, so one OAuth app can drive both. It maps a gatekeeper package name to the shell/`.dev.vars` variables that seed its `CLIENT_ID` and `CLIENT_SECRET`:

```js title="run-dev-server.js"
// Maps a gatekeeper name to the shared env vars whose values seed its CLIENT_ID / CLIENT_SECRET.
const SHARED_GATEKEEPER_CREDS = {
  "gatekeeper-github": { id: "GITHUB_CLIENT_ID", secret: "GITHUB_CLIENT_SECRET" },
  // …
};
```

Precedence rules:

- Gatekeepers with no entry in `SHARED_GATEKEEPER_CREDS` keep their raw config untouched.
- Credentials already defined in a gatekeeper's own config still win over the seeded values.
- `loadDevVars()` reads the root `.dev.vars` as `KEY=VALUE` lines into `process.env`, stripping surrounding single or double quotes; an existing shell environment value always takes precedence over the file. `.dev.vars` is gitignored and may hold local secrets.

<Note>
`run-dev-server.js` discovers connectors by scanning `packages/` for directories named `gatekeeper-*` that contain a `wrangler.jsonc`, and derives the service binding by uppercasing and replacing hyphens: `gatekeeper-github` → `GATEKEEPER_GITHUB`.
</Note>

## Declaring credentials for a hosted deploy

A connector declares the inputs the deploy flow should prompt for in `deploy-inputs.json` at the package root. Each entry describes one secret; the first entry may also carry the setup instructions and the redirect-URI template shown to the operator.

```json title="packages/gatekeeper-github/deploy-inputs.json"
[
  {
    "name": "CLIENT_ID",
    "kind": "secret",
    "label": "OAuth client ID",
    "consoleUrl": "https://github.com/settings/developers",
    "setupSteps": [
      "In GitHub Developer settings, create a new OAuth App (an OAuth App, not a GitHub App — GitHub Apps ignore OAuth scopes).",
      "Set the Homepage URL to your instance URL, and the Authorization callback URL to the redirect URI below.",
      "Register the application, then generate a new client secret.",
      "Copy the Client ID and client secret here."
    ],
    "redirectUriTemplate": "{PUBLIC_BASE_URL}/gatekeeper/github/oauth"
  },
  {
    "name": "CLIENT_SECRET",
    "kind": "secret",
    "label": "OAuth client secret"
  }
]
```

<ParamField body="name" type="string" required>
Environment variable the connector reads, e.g. `CLIENT_ID` or `CLIENT_SECRET`.
</ParamField>

<ParamField body="kind" type="string" required>
`"secret"` for credential values.
</ParamField>

<ParamField body="label" type="string" required>
Human-readable field label shown in the deploy flow.
</ParamField>

<ParamField body="consoleUrl" type="string">
Direct link to the provider console page where the OAuth app is registered.
</ParamField>

<ParamField body="setupSteps" type="string[]">
Ordered instructions for registering the OAuth app, rendered alongside the input.
</ParamField>

<ParamField body="redirectUriTemplate" type="string">
Callback URL to paste into the provider console, with `{PUBLIC_BASE_URL}` substituted for the instance base URL — e.g. `{PUBLIC_BASE_URL}/gatekeeper/github/oauth`.
</ParamField>

## Connectors that need no OAuth app

Not every connector has a provider to register with.

### Email

`packages/gatekeeper-email` *is* the service rather than a client of one: it implements a Cloudflare Email Worker that receives mail directly, so there are no `CLIENT_ID`/`CLIENT_SECRET` values. It is configured with a base URL instead.

<ParamField body="BASE_URL" type="string" required>
Full base URL (protocol + host + optional path, no trailing slash) at which the email gatekeeper's fetch handler is served. Every `http://localhost:8787/gatekeeper/email` in the local docs becomes this value in production.
</ParamField>

```ini
# Deployed as its own worker at the root:
BASE_URL=https://gatekeeper-email.example.workers.dev

# Or co-hosted on the same domain as the main app under a path:
BASE_URL=https://app.example.com/gatekeeper/email
```

Production also requires Cloudflare Email Routing: enable Email Routing for the domain and its DNS records, then create an **Email Routing → Email Workers** route (for example custom address `*@yourdomain.com`, action **Send to a Worker**, worker `gatekeeper-email`).

### MCP

`packages/gatekeeper-mcp` has nothing to configure per server. The user pastes an endpoint URL and the connector runs the OAuth discovery chain against it — protected resource metadata (RFC 9728) → authorization server metadata (RFC 8414) → dynamic client registration (RFC 7591) → authorization code with PKCE (RFC 7636) and a resource indicator (RFC 8707). An administrator's only lever is whether the connector is offered at all, in the Gatekeepers admin panel.

| Variable | Meaning |
| --- | --- |
| `BASE_URL` | Public base URL of this worker, for OAuth redirects. |
| `MCP_CLIENT_NAME` | Client name sent in `initialize` and dynamic client registration. |
| `MCP_ALLOW_INSECURE` | `"true"` disables the endpoint checks entirely: permits `http://` and private, loopback, link-local, and cloud-metadata hosts, on the endpoint and on every OAuth URL discovered from it. Local dev only. |

<Warning>
`MCP_ALLOW_INSECURE=true` is for local development only — it removes the host blocklist from both the endpoint and every OAuth URL discovered from it. Set it in the repo-root `.dev.vars` when connecting a server running on localhost.
</Warning>

## Verify the setup

<Steps>
<Step title="Start the dev server">
Start the application in dev mode from the repo root. Restart it after creating or editing a gatekeeper `.env`, since credentials are read into the worker config at startup.
</Step>
<Step title="Open a gadget and add a connection">
Create or open a gadget, go to the **Connections** tab, and click **+ New Connection**.
</Step>
<Step title="Pick a resource type">
Choose a resource type for the connector you configured — for GitHub: repository, issue, or pull request; for Google: Gmail, Google Doc, Google Spreadsheet, Google Calendar, or BigQuery.
</Step>
<Step title="Authorize">
If prompted, connect the account. The provider's authorization page opens in a new tab; after you grant access the tab closes and you return to Gadgets.
</Step>
<Step title="Select the resource and create the connection">
Use the picker to choose the specific repository, issue, pull request, document, or calendar. The gadget then has access only to the selected resource scope.
</Step>
</Steps>

Connected accounts can be listed, added, and removed in settings, reached from the account menu in the upper right.

## Troubleshooting

<AccordionGroup>
<Accordion title="&quot;Not configured&quot; page during authorization">
`CLIENT_ID` or `CLIENT_SECRET` is missing. Confirm the `.env` file exists at the connector's package directory (e.g. `packages/gatekeeper-github/.env`) and contains both values, then restart the dev server.
</Accordion>
<Accordion title="redirect_uri_mismatch">
The callback URL registered in the provider console does not match what the connector sends. It must be exactly `http://localhost:8787/gatekeeper/<name>/oauth` locally — no trailing slash, `http` not `https` — or `${PUBLIC_BASE_URL}/gatekeeper/<name>/oauth` when deployed.
</Accordion>
<Accordion title="bad_verification_code">
The authorization code has expired or was already used. Return to Gadgets and start the connection again.
</Accordion>
<Accordion title="Resource not accessible by integration">
You registered a GitHub **App** rather than an OAuth App, and it lacks the **Email addresses** account permission required for the sign-in email lookup. Switch to an OAuth App (recommended), or grant the App **Permissions & events → Account permissions → Email addresses → Read-only**, save, and have existing users re-run the sign-in flow to approve the added permission. Even with the permission granted, a GitHub App ignores `scope`, so sign-in cannot be limited to minimal scopes.
</Accordion>
<Accordion title="Google OAuth refuses the signing-in account">
While the Google app is in Testing mode, only accounts listed as Test Users on the OAuth consent screen can complete the flow. Add the address under **APIs & Services → OAuth consent screen → Add Users**.
</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Build a gatekeeper" href="/build-a-gatekeeper">
Add a connector package, declare its descriptions, and install it with a `GATEKEEPER_*` binding.
</Card>
<Card title="Configure sign-in and AI Gateway billing" href="/configure-signin-and-billing">
`AUTH_GATEKEEPERS` allowlisting, email-keyed identity, and incremental auth-versus-full scopes.
</Card>
<Card title="Environment variables" href="/environment-variables">
Every backend environment variable, including `PUBLIC_BASE_URL` and `AUTH_GATEKEEPERS`.
</Card>
<Card title="Local development" href="/local-development">
The two-terminal workflow, `.dev.vars` loading, and dynamic gatekeeper service-binding discovery.
</Card>
<Card title="Routing and worker bindings" href="/routing-and-bindings">
How `/gatekeeper/<name>/*` is derived from `GATEKEEPER_*` env keys and how inbound email is dispatched.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Known failure modes across the stack, including GitHub App versus OAuth App scope errors.
</Card>
</CardGroup>
