The GitHub-OAuth login module as a droppable busbar plugin: a cdylib exporting the auth C ABI (login-capable). Drop it in the plugins folder and add github to auth.chain with a browser_login block (the core injects client_secret).
| kind | alias | crate | busbar | license |
|---|---|---|---|---|
auth |
github |
busbar-auth-github-plugin |
1.6.0 (pinned in .busbar-ref) |
Apache-2.0 |
GitHub-OAuth login for busbar — a login-capable auth plugin (the auth kind's door, auth ABI
v3). A user signs in with GitHub; busbar establishes their identity as
github:<login> with github:org/<org> group memberships, then resolves those groups to policy
through the operator's auth.role_bindings.github: bindings.
It is a separate plugin from busbar-auth-oidc, and independent of the busbar core release.
GitHub OAuth issues an opaque access token and no id_token JWT, so the OIDC/JWKS
verify path does not apply — there is nothing to verify offline. Identity instead comes from
GitHub's REST API (/user, /user/orgs). On busbar 1.6 the plugin runs those hops itself over its
one outbound http need, and holds its own confidential-client secret (the kernel resolves
browser_login.client_secret into OpenIn.secrets).
| step | input | the plugin |
|---|---|---|
| begin | begin_login (kernel-minted state, PKCE code_challenge) |
answers the GitHub authorize URL |
| 1 | complete_login: code + redirect_uri + code_verifier |
POST .../login/oauth/access_token with its own client_secret |
| 2 | the token response | GET .../user (bearer) |
| 3 | the /user JSON |
GET .../user/orgs (or identifies now if fetch_orgs = false) |
| 4 | the /user/orgs JSON |
follows its Link: rel="next" page by page (on the API's origin, up to 10 pages), then identifies github:<login> + github:id/<id> + github:org/<org> groups |
Only a credential error is a declined sign-in (a bad credential, 401): the token endpoint's
400/401 carrying an RFC 6749 §5.2 error, its HTTP 200 answer carrying the user's own code
(bad_verification_code, invalid_grant, expired_token), and a 401 from /user or
/user/orgs (the access token is not accepted). Everything else is an outage (503, retryable):
GitHub answering 5xx or 429, its 403 rate limit (retry-after or x-ratelimit-remaining: 0),
a 403 on /user/orgs (the token lacks read:org: logged at warn as the operator's to fix), a
redirect, 404, 408 or any other status; a 200 token error that is the operator's
(incorrect_client_credentials, redirect_uri_mismatch, application_suspended, logged at warn)
or unknown; a 2xx with no token, no user or no org list (empty, HTML, malformed JSON); a hop that
cannot be made (refused, connect, TLS, timeout); a reply past 256 KiB. An outage carries GitHub's
retry hint (retry-after seconds, or the seconds to x-ratelimit-reset) for the host. An
id_token on a hop answer (never from github.com) must carry the nonce minted at begin, else the
sign-in fails its security check (400).
client_id, token_base and api_base are required, even for github.com: the hop allowlist
is built only from the URL strings written in the settings (1.5.5 parity), so validate/open
refuse settings that leave either at its default (settings.token_base: must be written out: the hop allowlist is built only from URLs in the settings). authorize_base is a browser redirect, not
a hop, and may stay at its default. An empty client_id, or a base that is not an http(s) URL with
a host, is refused too. The client_secret is never in this config — it is
browser_login.client_secret, which the kernel delivers to the plugin.
{
"client_id": "Iv1.abc123",
"scopes": ["read:org", "read:user"],
"api_base": "https://api.github.com",
"authorize_base": "https://github.com",
"token_base": "https://github.com",
"fetch_orgs": true
}| field | default | notes |
|---|---|---|
client_id |
(required) | GitHub OAuth App / GitHub App client id |
scopes |
["read:org","read:user"] |
read:org powers the org→group hop |
api_base |
(required) | https://api.github.com; GHES: https://<host>/api/v3 |
authorize_base |
https://github.com |
GHES: https://<host> |
token_base |
(required) | https://github.com; GHES: https://<host> |
fetch_orgs |
true |
false = login-only, no org groups, one fewer hop. true needs a scope that reads orgs (read:org, write:org, admin:org or user), else validate/open refuse it |
ca_cert_pem |
(none) | An extra trusted root (PEM) for a GHES behind an internal CA (see below) |
The config is deny_unknown_fields (a typo fails loudly at boot). Every refusal is the contract's
instance-relative line, settings.<path>: <reason> (settings: <reason> for the document), and
never quotes a value: a mistyped value is written *** (settings.client_id: invalid type: integer \***`, expected a string). A client secret that is not UTF-8 answers secrets: `client_secret` is not UTF-8`.
Point the three base URLs at your instance — authorize/token live at https://<host>/login/oauth/...
and the REST API at https://<host>/api/v3:
{
"client_id": "...",
"api_base": "https://ghe.corp.example/api/v3",
"authorize_base": "https://ghe.corp.example",
"token_base": "https://ghe.corp.example"
}Each org the user belongs to becomes a group string github:org/<org-login>, mapped to policy by the
operator's auth.role_bindings.github:.
cargo build --release # produces the cdylib plugin (libbusbar_auth_github_plugin.{so,dylib,dll})
cargo clippy --all-targets -- -D warnings
cargo test
The busbar-auth-github crate (auth-github/) is the reusable logic (statically linkable);
busbar-auth-github-plugin (auth-github-plugin/) is the thin cdylib that exports the auth C ABI via busbar_contract::export_door! (exporting
busbar_auth_github::door::door).
- PKCE. GitHub supports S256 PKCE on the web authorization-code flow (2024+). The core-minted
code_challengeis always sent; a GitHub OAuth App that has not opted into PKCE simply ignores it, and the pairedcode_verifierrides the token exchange harmlessly. - Request headers on GET hops. GitHub REST calls require an
Authorization: Bearer <token>and aUser-Agentheader.busbar_auth_github::userinfo_headers()gives the public ones (User-Agent,Accept), andbuild_userinfo_get/build_orgs_getattach them to the/userand/user/orgshops. Headers are sanitized (CR/LF/NUL and hop-control headers rejected) and a hop only goes to a host on the allowlist built from the settings' URLs. - Credentials are the SDK chip's. The client secret and the access token are
abi::sdk::cred::Creds: the secret goes out only as a sealedFormBodyvalue, the token only as the sealedauthorization: Bearerfield, both written by the SDK at the wire and required to go out on a secured connection when their base ishttps. The plugin holds neither as text; the code, the verifier and every hop answer are wiped when they go, and nothing formats them. ca_cert_pem. The Statement declares two outboundhttpneeds: one trustingsettings.ca_cert_pembeside the public roots, one the public roots only. A config that names aca_cert_pemgoes out on the first, any other on the second; the host's connector does the TLS, and certificate validation is only widened, never disabled. A value with no PEM certificate in it is refused (settings.ca_cert_pem: not a PEM certificate (no -----BEGIN CERTIFICATE----- line)), and so is one written blank (settings.ca_cert_pem: blank: omit it, or give a PEM certificate).- Multi-hop flow state. The access token is seen once (the token response) but is needed to
authenticate both GETs, and the
/useridentity is needed at the/user/orgsstep. Each flow (LoginFlow) owns this state, so concurrent logins never share a slot; a finished flow holds neither. - Org pages.
/user/orgsis read 100 orgs a page, following GitHub'sLink: rel="next"on theapi_baseorigin (a next link anywhere else is an outage, never followed), for at most 10 pages (1000 orgs). A user in more orgs than that signs in with the first 1000 org groups, and the plugin logs a WARN that the org list was truncated (fail-safe: fewer roles, never more). - Reply cap. Every hop's reply is capped at 256 KiB (
MAX_HOP_REPLY_BYTES, the exchange'sreply_limit); a reply past it ends the login as an outage.
cargo test --workspace --lockedauth-github-plugin/tests/conformance.rs runs busbar's published conformance suite (both doors,
the far ends in conformance.json). tests/e2e.rs is the live end-to-end against a real busbar
and a wiremock GitHub (CI's wiremock service).
Apache-2.0. See LICENSE.