Google and GitHub sign-in for every deployment of your app: production, each pull request preview,
each dev checkout. One Google client serves them all. The hub keeps no user accounts: each app gets
a standard OpenID Connect id_token and owns its users.
You want sign-in in your app. You do not need to run a hub. Samebase runs this repository,
unchanged, as a public hub at id.samebase.com, and an app registers there from its own GitHub
Actions run, with no account. Start from samebase/base, where it
works on the first deploy. In its Alchemy stack it is one resource:
IdentityHub.Client("IdentityHubClient", {
hub: { issuer: "https://id.samebase.com" },
connectors: ["google", "github"],
redirectUris: [Output.interpolate`${deployment.siteUrl}/auth/hub/callback`],
deploymentType: stage === "prod" ? "production" : "preview",
});For an app that does not come from the starter, the broker's consumer guide has the steps. The public hub is a shared service under Samebase's terms.
You want your own hub. Your name on the sign-in page, your Google project, and your rules about who may register. This repository is the template: run your own hub.
- Providers want one callback URL. Google and GitHub need each redirect URI registered by hand, so a preview deployment with a fresh URL cannot have Google sign-in. The hub registers that URL itself, at deploy time.
- No account, no token. An app registers in its own GitHub Actions run, with GitHub's OIDC token
as proof of its repository.
HUB_OWNERSdecides which GitHub accounts may register. - Users are portable. A user's
subis the provider's own id,google:<id>orgithub:<id>, the same at every hub. An app can move to another hub and keep its users.
The hub is @samebase/identity-broker
plus the settings of this repository.
You need a Convex team, a Google Cloud project, and a GitHub account.
Select Use this template on this page.
In the repository, Settings, Secrets and variables, Actions:
| Name | Kind | Value |
|---|---|---|
CONVEX_ACCESS_TOKEN |
secret | A Convex team access token (Convex dashboard, team settings, Access Tokens). |
HUB_OWNERS |
variable | any, or the numeric ids of the GitHub accounts whose repositories may register, separated by commas. |
HUB_NAME |
variable | The name of your sign-in service, shown on the sign-in page. |
HUB_TERMS_URL |
variable | Your terms for the sign-in service. |
HUB_PRIVACY_URL |
variable | Your privacy notice for the sign-in service. |
HUB_PROJECT |
variable | Optional. The Convex project name. Default: the repository name. |
IDENTITY_HUB_MODERATION_KEY |
secret | Optional. A long random key for the /moderation/* API. |
A GitHub account's numeric id is the id field at https://api.github.com/users/<login>.
Actions, alchemy, Run workflow, on main. The run creates the Convex project and an
empty production deployment, and prints its url and deployment name. It pushes no code yet. If
the project name is taken, the run stops: set HUB_PROJECT to another name and run it again.
The issuer is the deployment's url, or a custom domain. Every app and both
providers use it, so decide now.
- Google Cloud console, Google Auth Platform, Clients: a Web application client with the
redirect URI
<issuer>/connectors/google/callback. Publish the app and add your domain to the branding when you open the hub to other people. - GitHub, Settings, Developer settings, OAuth Apps: a new OAuth App with the callback URL
<issuer>/connectors/github/callback.
Save them as the secrets GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, AUTH_GITHUB_CLIENT_ID, and
AUTH_GITHUB_CLIENT_SECRET.
From a clone of the repository, with the deployment name from step 3:
pnpm install
npx convex login
env -u CONVEX_DEPLOY_KEY pnpm run keys:init --deployment <deployment>The script refuses a deployment that already has keys. Back up the value it writes,
IDENTITY_BROKER_KEYS, somewhere safe.
This run pushes the hub. <issuer>/.well-known/openid-configuration now answers with your issuer.
Every push to main deploys again. After you change a secret or a variable, run the workflow by
hand: saving a secret does not start a run.
An app from samebase/base names its hub in alchemy.run.ts:
const HUB = { issuer: "https://<your issuer>", terms: "<your HUB_TERMS_URL>" };Its next deploy registers its clients here. Any other app uses the IdentityHub.Client resource of
@samebase/alchemy-convex, with
connectors: ["google", "github"]. The broker's
consumer guide
has the details.
Custom domains need a Convex plan that includes them. Add the domain in the Convex dashboard (the deployment, Settings, Custom Domains), then create both DNS records it asks for:
| Type | Name | Value |
|---|---|---|
| CNAME | <domain> |
convex.domains (DNS only, no proxy) |
| TXT | _convex_domains.<domain> |
the deployment name that step 3 printed |
Convex verifies the domain only with the TXT record. Wait until https://<domain> serves HTTPS.
Then set the variable ISSUER to https://<domain>, register the callbacks for that domain at
Google and GitHub, and run the workflow.
Do it before the first app registers. Changing the issuer later is a move: every app registers again and needs the new callbacks.
-
Pause every app: set
APPS_PAUSED=trueon the deployment (Convex dashboard, Environment Variables). Remove it to resume. -
List and delete:
npx convex run --deployment <deployment> broker:listApps, thenbroker:listClients '{"appId":"..."}',broker:deleteClient '{"clientId":"..."}',broker:deleteApp '{"appId":"..."}'. -
Block a repository or an owner at once, whatever
HUB_OWNERSsays:curl -X POST <issuer>/moderation/blocks \ -H "Authorization: Bearer $IDENTITY_HUB_MODERATION_KEY" -H "Content-Type: application/json" \ -d '{"action":"block","kind":"repository","githubId":"<numeric id>","reason":"<why>","adminEmail":"<you>"}'
"kind":"owner"blocks every repository of an account, and"action":"unblock"lifts a block. The other endpoints are in the broker's moderation API. -
A daily cron removes expired requests, old sign-in records, and preview and development clients that no deploy renewed for 14 days. A production client stays until you delete it.
-
A repository that changes owner stays under its last recorded owner until it registers again or you block it.
-
The deploy key and
CONVEX_ACCESS_TOKENcan read the signing keys. Treat both as administrator credentials. -
Never run
npx convex import --replace-allon the hub: it also deletes the components' rows.
Renovate opens a pull request when a new broker or alchemy-convex version is out. Read the broker's changelog before you merge: a release that changes stored data says what to do.
- No user accounts, no sessions, no passwords. Every sign-in goes to Google or GitHub again, and
the app makes its own user from the
id_token. - What it does store: the apps, their clients, and short-lived sign-in records. A daily cron removes the expired ones.
- Standards: OAuth 2.0 with PKCE and exact redirect URIs, OpenID Connect Core and Discovery, RFC 8414, RFC 9207, and RFC 9700. The broker's standards list has each one with its sections, and its design notes name the departures.
- In production:
id.samebase.comruns this repository unchanged.
Report a vulnerability in private: see SECURITY.md.
MIT. Copy it, change it, ship it; keep the copyright line.
