Documentation
¶
Overview ¶
Package githubadapter provides a hardened GitHub implementation of the credbound.SSOProvider port. GitHub is not an OpenID Connect issuer — it speaks plain OAuth 2.0 and identity comes from its REST API — so the generic OIDC adapter (ssoadapter) cannot serve it; this package owns that protocol gap with the standard library only.
Responsibilities ¶
The adapter owns the protocol exchange only: the authorization redirect (with a random state, a PKCE S256 challenge, and allow_signup=false), the code exchange at GitHub's token endpoint (presenting the PKCE verifier), and the REST reads of GET /user and GET /user/emails. Credbound keeps everything else — the sealed continuation carrying the adapter's opaque session, ceremony TTL, identity linking, persistence, audit, and revocation.
Registration ¶
Register a provider by wiring it into credbound.Config.SSOProviders:
provider, err := githubadapter.New(githubadapter.Config{
ConfigurationID: "0198b463-51a2-7cde-8000-0123456789ab", // UUIDv7 chosen by the host
ClientID: os.Getenv("GITHUB_CLIENT_ID"),
ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"),
RedirectURL: "https://app.example.com/sso/callback",
})
if err != nil {
log.Fatal(err)
}
manager, err := credbound.New(credbound.Config{
Store: store,
Passwords: hasher,
SecretKey: secretKey,
SSOProviders: []credbound.SSOProvider{provider},
})
The provider's kind is always credbound.SSOProviderGitHub.
Callback handling ¶
The host's HTTP callback handler forwards the provider response verbatim to credbound's FinishSSO. The adapter accepts the full callback URL, the bare query string, or a JSON object with "code" and "state" fields:
func callback(w http.ResponseWriter, r *http.Request) {
continuation := readContinuationCookie(r)
auth, err := manager.FinishSSO(r.Context(), continuation, []byte(r.URL.String()))
// ...
}
Subject and email trust ¶
The credbound subject is the decimal form of GitHub's numeric account id — the only stable identifier GitHub offers. The login is never used: logins are renameable and can be re-registered by someone else, which would let a freed name inherit a victim's linked identity. The primary email from GET /user/emails is forwarded with GitHub's own verified flag; when the user:email scope was declined the adapter falls back to the public profile email as unverified. Credbound keys SSO identities on issuer and subject, never on email, so an absent or unverified address only limits convenience.
No step-up, AAL1 only ¶
GitHub's authorization endpoint has no prompt=login or max_age equivalent: an authorization silently reuses whatever browser session GitHub already holds, and nothing in the response proves a fresh login. Begin therefore fails with ErrStepUpUnsupported when SSORequest.ForceReauthentication is set — a step-up ceremony must not pretend a re-authentication happened. For the same reason the adapter asserts no ACR or AMR, so without an explicit credbound.Config.SSOAssurance opt-out (TrustUnverified) a GitHub sign-in grants only AAL1. That is the correct posture: the adapter cannot verify that GitHub enforced a second factor. Hosts with hard step-up or MFA-assurance requirements should pair GitHub sign-in with a credbound second factor, or prefer an OIDC provider (ssoadapter) for those flows.
Index ¶
- Variables
- type Config
- type Provider
- func (p *Provider) Begin(_ context.Context, request credbound.SSORequest) (credbound.SSOProviderChallenge, error)
- func (p *Provider) ConfigurationID() credbound.UUID
- func (p *Provider) Finish(ctx context.Context, sessionBytes, response []byte) (credbound.SSOClaims, error)
- func (p *Provider) Kind() credbound.SSOProviderKind
Constants ¶
This section is empty.
Variables ¶
var ( // ErrStateMismatch reports that the state parameter returned by GitHub // does not match the one issued in Begin. ErrStateMismatch = errors.New("githubadapter: state parameter mismatch") // ErrStepUpUnsupported reports a Begin with ForceReauthentication set: // GitHub's authorization endpoint cannot force a fresh login, so the // adapter refuses to run a step-up ceremony it cannot honor rather than // silently reusing GitHub's existing browser session. ErrStepUpUnsupported = errors.New("githubadapter: github cannot force re-authentication; step-up ceremonies must use another provider") )
Sentinel errors for the failures hosts most often want to distinguish in logs. Credbound maps every Begin/Finish error to ErrInvalidCredentials before it reaches the end user.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// ConfigurationID is the host-chosen UUIDv7 under which credbound
// indexes this provider and its linked identities. Required.
ConfigurationID credbound.UUID
// ClientID is the OAuth app's client identifier. Required.
ClientID string
// ClientSecret authenticates the client at the token endpoint.
// Required: GitHub OAuth apps are confidential clients.
ClientSecret string
// RedirectURL is the callback URL registered at GitHub. Required.
// HTTPS is mandatory except for loopback hosts, which may use HTTP for
// local development and tests.
RedirectURL string
// Scopes defaults to "read:user user:email" — enough to read the stable
// numeric account id and the primary email with its verified flag.
Scopes []string
// HTTPClient is used for the code exchange and the REST calls. Defaults
// to a client with a 10 second timeout; a supplied client without a
// timeout is shallow-copied and given the default.
HTTPClient *http.Client
// AuthorizeURL overrides GitHub's authorization endpoint, for tests.
// Defaults to https://github.com/login/oauth/authorize.
AuthorizeURL string
// TokenURL overrides GitHub's token endpoint, for tests. Defaults to
// https://github.com/login/oauth/access_token.
TokenURL string
// APIBaseURL overrides the GitHub REST API base, for tests. Defaults to
// https://api.github.com.
APIBaseURL string
}
Config describes one GitHub OAuth app registration.
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider is the GitHub implementation of credbound.SSOProvider. It is stateless across ceremonies: everything a Finish needs travels inside the opaque Session bytes that credbound seals into its continuation.
func New ¶
New validates the configuration and returns a Provider ready to register in credbound.Config.SSOProviders. Construction performs no network calls.
func (*Provider) Begin ¶
func (p *Provider) Begin(_ context.Context, request credbound.SSORequest) (credbound.SSOProviderChallenge, error)
Begin implements credbound.SSOProvider. It generates a fresh state and a PKCE S256 challenge, builds the GitHub authorization URL (with allow_signup=false so the ceremony only authenticates existing GitHub accounts), and returns the state and verifier as opaque Session bytes for credbound to seal into its continuation.
A request with ForceReauthentication set fails with ErrStepUpUnsupported: GitHub's authorization endpoint has no prompt/max_age equivalent, so the adapter cannot make GitHub re-verify the user, and a step-up ceremony must not pretend it did.
func (*Provider) ConfigurationID ¶
ConfigurationID implements credbound.SSOProvider.
func (*Provider) Finish ¶
func (p *Provider) Finish(ctx context.Context, sessionBytes, response []byte) (credbound.SSOClaims, error)
Finish implements credbound.SSOProvider. sessionBytes is the Session issued by Begin (returned by credbound from its sealed continuation) and response is the raw callback payload from the host: the full callback URL, the bare query string, or a JSON object with code and state fields.
Finish verifies the state with a constant-time comparison, exchanges the code at the token endpoint, then reads the account through the REST API: the numeric account id becomes the subject (logins are renameable and never used) and the primary email is forwarded with GitHub's own verified flag. Replay of a callback is bounded by credbound's continuation TTL and by GitHub's single-use authorization code; the adapter itself keeps no state.
func (*Provider) Kind ¶
func (p *Provider) Kind() credbound.SSOProviderKind
Kind implements credbound.SSOProvider.