Plugin Store · Identity provider
Developers sign in with GitHub for a budgeted key.
Overview
The GitHub auth method turns a GitHub sign-in into the way developers get a busbar key: busbar hosts token-exchange itself, so a dev signs in with GitHub and gets back their own key. A dev opens GET /auth/token, signs in with GitHub, and busbar hands back their own static, self-scoped key, its budget auto-provisioned from the team’s child_default on first exchange, the secret shown on sign-in and again any time you sign in.
GitHub is OAuth, not OIDC: it issues an opaque access token and no id_token JWT, so there is nothing to verify offline against a JWKS. Identity comes from the GitHub REST API instead: after the OAuth code exchange, busbar reads GET /user for the account (identity of record is the GitHub login → github:<login>) and GET /user/orgs for org membership, which becomes github:org/<org> groups. role_bindings.github then maps those org groups to a busbar team, and therefore to that team’s budget and pools.
The GitHub OAuth App’s confidential client secret is busbar’s alone: it lives in the method’s browser_login.client_secret and busbar injects it only into the token-exchange step; the plugin writes the key, never the value. The login uses the authorization-code flow with PKCE (S256).
It’s one key per person, not a key list: the key is deterministic, derived from the signed-in identity under auth.signing_key. Re-login just shows a dev their same key again, next to a Refresh button that rotates it (the old key dies, the new one takes its place). How long an issued key stays valid is auth.key_ttl (default 90d): an admin-set config value, never a per-dev choice. See How token-exchange works for the shared token system behind this.
Define the provider under identity-providers: with module: github (the map key is the provider instance name, which role_bindings and auth.chain reference it by) and busbar exposes the exchange automatically. Its browser_login block is the switch for the hosted sign-in button; because it carries the OAuth client secret, browser login for GitHub always needs it. The button’s label and GitHub icon are inferred; nothing to set. Define a second provider (e.g. an SSO issuer) with its own browser_login and the login page becomes a chooser.
For GitHub Enterprise Server, point the three base URLs at your instance: api_base: https://<host>/api/v3, authorize_base: https://<host>, and token_base: https://<host>. Everything else is identical.
Install it
The GitHub provider ships as a signed kind: auth plugin. Let busbar auto-download it on boot: add it to plugins.fetch (verified by sha256 + ed25519 signature + allowlist before load), set your public_url, and define the provider:
public_url: "https://busbar.example.com" # busbar builds /auth/token itself
plugins:
enabled: true
fetch:
- github: GetBusbar/[email protected] # auto-downloaded, version-pinned
identity-providers:
github: # map key == module name: must be "github"
module: github
settings:
client_id: "<github-oauth-client-id>"
scopes: [read:org, read:user] # defaults; read:org powers the org → groups hop
browser_login: # present → "Continue with GitHub" button
client_secret: { env: GITHUB_OAUTH_SECRET }
auth:
signing_key: { file: /run/secrets/busbar-signing.key }
chain: [keys] # devs' issued keys authenticate here
key_ttl: 90d # admin-set; default 90d, no dev choice
role_bindings:
github:
"github:org/<org>": { group: engineering } New in busbar 1.5.2 (token-exchange). The plugin is independently versioned from busbar itself; pin both explicitly in production (the @1.0.0 in fetch is the pin).
Prefer to install by hand? No plugins.fetch config needed: download the signed tarball (links above), drop it in your plugins.dir, and busbar loads it at boot; signature and trust checks still apply. You can also push it live over the admin API. See the plugin install docs.
Settings
| Setting | Required | Default | Description |
|---|---|---|---|
client_id | Yes | — | The GitHub OAuth App (or GitHub App) client id. Presented on the authorize URL and the token exchange. The matching client secret is never here; it’s browser_login.client_secret, held by busbar. |
scopes | No | [read:org, read:user] | read:org lets the /user/orgs hop enumerate org memberships into github:org/<org> groups; read:user reads the /user profile. Request-time extras are folded in and de-duplicated. |
api_base | No | https://api.github.com | REST API base (/user, /user/orgs). For GitHub Enterprise Server set https://<host>/api/v3. |
authorize_base | No | https://github.com | Web base for the authorize endpoint (/login/oauth/authorize). For GHES set https://<host>. |
token_base | No | https://github.com | Web base for the token endpoint (/login/oauth/access_token). For GHES set https://<host>. |
fetch_orgs | No | true | Chain the /user/orgs hop and populate github:org/<org> groups. Set false to establish identity from /user alone (no groups, one fewer hop). |
ca_cert_pem | No | — | Additional trusted root CA (PEM) for a GHES instance behind an internal CA. Accepted for forward-compatibility; on the committed 1.5.2 ABI the core executes every hop, so this is captured but not yet delivered to the hop executor. |
browser_login | No | — | Presence is the switch for the hosted sign-in button on GET /auth/token. Because GitHub is a redirect/OAuth method, browser login requires client_secret inside it. |
browser_login.client_secret | Yes | — | Required whenever browser_login is set: the GitHub OAuth App’s confidential client secret, as a secret reference ({ env: } / { file: }), never a plaintext literal. busbar injects it only into the token-exchange step. |
Configuration scenarios
When to use: A developer with just a browser should be able to get a key by signing in with GitHub. busbar hosts the sign-in and hands back the dev’s own budgeted key, ideal for Cursor / Claude Code / VSCode BYOK, where the tool just needs a static key and a base URL.
public_url: "https://busbar.example.com" # busbar builds /auth/token itself
plugins:
enabled: true
fetch:
- github: GetBusbar/[email protected]
identity-providers:
github:
module: github
settings:
client_id: "<github-oauth-client-id>"
browser_login: # presence = "Continue with GitHub" button
client_secret: { env: GITHUB_OAUTH_SECRET }
auth:
signing_key: { file: /run/secrets/busbar-signing.key }
chain: [keys] # the issued key authenticates here
key_ttl: 90d # admin-set; default 90d, no dev choice
role_bindings:
github:
"github:org/<org>": { group: engineering } A dev opens GET /auth/token (busbar builds the URL from public_url), clicks Continue with GitHub, authorizes, and busbar returns their own static key: self-scoped github:<login>, budget auto-provisioned from the team’s child_default on first login, the secret shown then and re-shown on every later sign-in. The button label + GitHub icon are inferred; there’s nothing to name.
When to use: Access should follow GitHub org membership: everyone in your GitHub org gets the engineering team’s budget and pools, no per-dev setup.
public_url: "https://busbar.example.com"
plugins:
enabled: true
fetch:
- github: GetBusbar/[email protected]
identity-providers:
github:
module: github
settings:
client_id: "<github-oauth-client-id>"
scopes: [read:org, read:user] # read:org is what powers the org hop
browser_login:
client_secret: { env: GITHUB_OAUTH_SECRET }
auth:
signing_key: { file: /run/secrets/busbar-signing.key }
chain: [keys]
key_ttl: 90d
role_bindings:
github:
"github:org/acme": { group: engineering }
"github:org/acme-ops": { group: platform } After sign-in busbar reads GET /user/orgs and emits one github:org/<org> group per org the dev belongs to; role_bindings.github maps each to a busbar team. Note: the shipped plugin emits org groups only (github:org/<org>); it does not fetch or emit per-team (github:team/<org>/<team>) groups, so bind on orgs. Set fetch_orgs: false to skip this hop entirely (identity from /user alone, no groups).
When to use: Your developers live on a self-hosted GitHub Enterprise Server, not github.com. Point the three base URLs at your instance; the token-exchange + identity flow is otherwise unchanged.
public_url: "https://busbar.example.com"
plugins:
enabled: true
fetch:
- github: GetBusbar/[email protected]
identity-providers:
github:
module: github
settings:
client_id: "<ghes-oauth-client-id>"
api_base: "https://ghe.corp.example/api/v3" # REST API base (note /api/v3)
authorize_base: "https://ghe.corp.example" # /login/oauth/authorize
token_base: "https://ghe.corp.example" # /login/oauth/access_token
ca_cert_pem: { file: /etc/busbar/ghes-ca.pem } # if GHES is behind an internal CA
browser_login:
client_secret: { env: GITHUB_OAUTH_SECRET }
auth:
signing_key: { file: /run/secrets/busbar-signing.key }
chain: [keys]
key_ttl: 90d
role_bindings:
github:
"github:org/<org>": { group: engineering } The three *_base overrides are the whole change: api_base gets GHES’s /api/v3 REST root, authorize_base/token_base get its web host. Note: ca_cert_pem is accepted for forward-compatibility but on the committed 1.5.2 ABI the core executes the hops and has no channel to receive it yet, so a GHES behind a private CA needs that CA trusted at the busbar host level for now.
When to use: You just want a GitHub sign-in to mint a key and don’t map org membership to teams, e.g. a small team, a personal instance, or where every dev should land on one default team. Skip the org hop entirely.
public_url: "https://busbar.example.com"
plugins:
enabled: true
fetch:
- github: GetBusbar/[email protected]
identity-providers:
github:
module: github
settings:
client_id: "<github-oauth-client-id>"
scopes: [read:user] # drop read:org, no org enumeration
fetch_orgs: false # skip the /user/orgs hop; no groups emitted
browser_login:
client_secret: { env: GITHUB_OAUTH_SECRET }
auth:
signing_key: { file: /run/secrets/busbar-signing.key }
chain: [keys]
key_ttl: 90d
# no role_bindings.github needed: with no groups, every dev lands on the default team fetch_orgs: false establishes identity from GET /user alone (one fewer hop) and emits no github:org/<org> groups, so there is nothing for role_bindings.github to match and every signed-in dev gets the default team’s budget. Dropping read:org from scopes narrows what busbar asks GitHub for, since the org read is no longer needed. Identity is still github:<login>.