githubauth

package module
v1.9.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 19, 2026 License: MIT Imports: 22 Imported by: 16

README

go-githubauth

GoDoc Test Status codecov Mentioned in Awesome Go

GitHub authentication for Go, exposed as standard oauth2.TokenSource implementations: GitHub App JWTs, installation tokens, and personal access tokens. Depends only on golang-jwt/jwt and golang.org/x/oauth2 — no GitHub SDK required.

Installation

go get github.com/jferrl/go-githubauth

Requires Go 1.26+.

Quick start

Authenticating as a GitHub App is a two-step chain: an RS256 JWT identifies the App, and it is exchanged for an installation token scoped to one installation. Both sources cache their tokens and refresh them proactively.

privateKey := []byte(os.Getenv("GITHUB_APP_PRIVATE_KEY"))
clientID := os.Getenv("GITHUB_APP_CLIENT_ID") // e.g. "Iv1.1234567890abcdef"
installationID, _ := strconv.ParseInt(os.Getenv("GITHUB_INSTALLATION_ID"), 10, 64)

appTokenSource, err := githubauth.NewApplicationTokenSource(clientID, privateKey)
if err != nil {
	log.Fatal(err)
}
installationTokenSource := githubauth.NewInstallationTokenSource(installationID, appTokenSource)

// Every request carries a valid installation token; refresh is automatic.
// Works standalone or with any SDK that accepts an *http.Client, e.g.
// github.NewClient(httpClient) from google/go-github.
httpClient := oauth2.NewClient(context.Background(), installationTokenSource)

NewApplicationTokenSource accepts a string Client ID (recommended by GitHub) or an int64 App ID (legacy) — the type is inferred from the argument. Runnable examples for every constructor live on pkg.go.dev.

Features

  • oauth2.TokenSource implementations for GitHub App JWTs, installation tokens, and personal access tokens (classic and fine-grained)
  • Token caching with proactive refresh: tokens regenerate 30s before expiry, eliminating in-flight 401s (tunable via WithExpirySkew / WithInstallationExpirySkew)
  • JWT signing through the standard crypto.Signer interface, so the private key can live in AWS KMS, GCP KMS, Azure Key Vault, Vault Transit, a PKCS#11 HSM, or ssh-agent
  • Webhook delivery verification (X-Hub-Signature-256, constant-time) with ready-made http.Handler middleware
  • GitHub Enterprise Server and GitHub Enterprise Cloud (data residency) support
  • Automatic single retry on throttled responses (WithRetryOnThrottle, enabled by default)
  • Two dependencies total: golang-jwt/jwt and golang.org/x/oauth2

Used by

Project
Kargo Application lifecycle orchestration
Terraform GitHub provider The Terraform provider built and run by GitHub
gno Go virtual machine and blockchain behind gno.land
Updatecli Declarative update policy engine
Sippy Dashboards for OpenShift CI test and job data

Full list on pkg.go.dev.

Command line

The same credentials, without writing Go:

brew install jferrl/tap/githubauth

Or download a binary from the latest release — Linux, macOS and Windows, on amd64 and arm64 — or build it yourself:

go install github.com/jferrl/go-githubauth/cmd/githubauth@latest
githubauth token --client-id Iv1.abc --key app.pem --installation 12345

The token goes to stdout and nothing else does, so it composes:

curl -H "Authorization: Bearer $(githubauth token)" \
  https://api.github.com/installation/repositories

Every flag falls back to an environment variable — GITHUB_APP_CLIENT_ID, GITHUB_APP_PRIVATE_KEY, GITHUB_APP_INSTALLATION_ID — so a configured CI step is just githubauth token. --key takes a file path, the PEM itself, or - to read stdin, which keeps the key off disk:

vault kv get -field=pem secret/github-app |
  githubauth token --key - --installation 12345

githubauth jwt prints the App JWT for the few endpoints that need one, --json adds the expiry, and --repos scopes the token to named repositories. Run githubauth help for the rest.

Comparison with ghinstallation

ghinstallation is the long-standing library in this space and works well. The core difference is the integration model: ghinstallation is an http.RoundTripper you install as an HTTP transport, while go-githubauth implements oauth2.TokenSource, so credentials compose with anything that speaks oauth2 — oauth2.NewClient, go-github, gRPC per-RPC credentials, or code that just needs the token string.

go-githubauth ghinstallation
Integration model oauth2.TokenSource http.RoundTripper
Dependencies golang-jwt/jwt, x/oauth2 golang-jwt/jwt, google/go-github
App identifiers Client ID (string, recommended by GitHub) and App ID (int64) App ID (int64)
Token refresh Proactive, tunable (WithExpirySkew, default 30s) Proactive, fixed 1 minute
External signers (KMS/HSM) Standard crypto.Signer — existing KMS adapters plug in directly Library-specific Signer interface
Webhook signature verification Included (webhook subpackage) Not included
Personal access tokens Included Not included
GitHub Enterprise WithEnterpriseURL (GHES) and WithBaseURL (GHEC data residency) BaseURL field

If ghinstallation already fits your setup, there is no urgent reason to switch. Choose go-githubauth when you want oauth2-native composition, Client ID support, KMS-backed signing through the standard crypto.Signer interface, or a smaller dependency tree.

Personal access tokens

tokenSource := githubauth.NewPersonalAccessTokenSource(os.Getenv("GITHUB_TOKEN"))
httpClient := oauth2.NewClient(context.Background(), tokenSource)

Works with both classic (ghp_...) and fine-grained (github_pat_...) tokens.

GitHub Enterprise

  • WithEnterpriseURL — GitHub Enterprise Server (GHES). The URL is normalized the way GHES expects, appending /api/v3/ when needed.
  • WithBaseURL — the URL is used verbatim. Fits GitHub Enterprise Cloud with data residency (https://api.SUBDOMAIN.ghe.com/) or an httptest server in tests.
githubauth.NewInstallationTokenSource(installationID, appTokenSource,
	githubauth.WithEnterpriseURL("https://github.example.com"))

githubauth.NewInstallationTokenSource(installationID, appTokenSource,
	githubauth.WithBaseURL("https://api.octocorp.ghe.com"))

Options combine in any order. An unparseable URL (or a nil client passed to WithHTTPClient) is reported by the first Token() call instead of silently falling back to the public GitHub API.

Proactive token refresh

oauth2.ReuseTokenSource refreshes a cached token only after it expires, so a request that starts just before expiry can reach GitHub with a dead credential and 401. Both constructors instead wrap their sources in ReuseTokenSourceWithSkew, refreshing when time.Until(exp) <= skew (default DefaultExpirySkew, 30s).

appTokenSource, err := githubauth.NewApplicationTokenSource(clientID, privateKey,
	githubauth.WithApplicationTokenExpiration(5*time.Minute),
	githubauth.WithExpirySkew(5*time.Second), // effective validity: 3m55s
)

Expiration is backdated 60s for clock drift, so effective validity is expiration - 60s - skew. Values at or below 90s (the backdate plus DefaultExpirySkew) are rejected and fall back to 10 minutes, because below that the cache can never hold the token and every call re-signs.

A zero or negative skew restores exact oauth2.ReuseTokenSource behavior. The wrapper is exported as ReuseTokenSourceWithSkew for use with any third-party oauth2.TokenSource, and is safe for concurrent use.

Signing with external key stores (KMS, HSM, Vault)

NewApplicationTokenSourceFromSigner accepts any RSA-backed crypto.Signer, so the App private key never touches process memory. GitHub requires RS256; non-RSA signers are rejected at construction time.

// signer: *rsa.PrivateKey, or a wrapper for AWS KMS, GCP KMS, Azure Key
// Vault, Vault Transit, a PKCS#11 HSM, or ssh-agent.
appTokenSource, err := githubauth.NewApplicationTokenSourceFromSigner(clientID, signer)

All major backends support the required RSASSA_PKCS1_V1_5_SHA_256 operation: AWS KMS, GCP KMS, Azure Key Vault, Vault Transit, and PKCS#11 via crypto11. Community crypto.Signer adapters: form3tech-oss/jwt-go-aws-kms, salrashid123/signer.

Webhook verification

The webhook subpackage verifies the X-Hub-Signature-256 header (HMAC-SHA256, constant time) and ships middleware that restores the body for downstream handlers. Failed verifications short-circuit with 401; oversized bodies return 413.

secret := []byte(os.Getenv("GITHUB_WEBHOOK_SECRET"))

mux := http.NewServeMux()
mux.HandleFunc("/webhook", handleWebhook) // body is already authenticated here

log.Fatal(http.ListenAndServe(":8080", webhook.Middleware(secret)(mux)))

Options: webhook.WithMaxPayloadSize(n) (default 25 MiB, GitHub's delivery cap) and webhook.WithErrorHandler(fn).

Outside net/http (Lambda, queues), use webhook.Verify directly:

if err := webhook.Verify(secret, body, signature); err != nil {
	// branch with errors.Is: webhook.ErrMissingSignature,
	// webhook.ErrInvalidSignatureFormat, webhook.ErrSignatureMismatch
}

Contributing

Contributions are welcome! Please open an issue or submit a pull request on GitHub. If this package is useful to you, a star helps others discover it.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Documentation

Overview

Package githubauth provides GitHub authentication as standard golang.org/x/oauth2.TokenSource implementations: GitHub App JWTs, GitHub App installation tokens, and personal access tokens.

Because every credential is exposed through the oauth2.TokenSource interface, the package composes with anything that accepts one — golang.org/x/oauth2.NewClient, the google/go-github SDK, gRPC per-RPC credentials, or code that simply needs the token string. The module depends only on github.com/golang-jwt/jwt and golang.org/x/oauth2; no GitHub SDK is required.

GitHub App authentication

Authenticating as a GitHub App is a two-step chain: a short-lived RS256-signed JWT identifies the App itself, and that JWT is exchanged for an installation access token scoped to one installation of the App.

privateKey := []byte(os.Getenv("GITHUB_APP_PRIVATE_KEY"))
clientID := os.Getenv("GITHUB_APP_CLIENT_ID")
installationID, _ := strconv.ParseInt(os.Getenv("GITHUB_INSTALLATION_ID"), 10, 64)

appSource, err := githubauth.NewApplicationTokenSource(clientID, privateKey)
if err != nil {
	// handle error
}
installationSource := githubauth.NewInstallationTokenSource(installationID, appSource)

// httpClient authenticates every request with a fresh installation token.
httpClient := oauth2.NewClient(context.Background(), installationSource)

NewApplicationTokenSource accepts either a string Client ID (recommended by GitHub for new Apps) or an int64 App ID (legacy); the type parameter is inferred from the argument.

Token caching and proactive refresh

Both NewApplicationTokenSource and NewInstallationTokenSource wrap their sources in ReuseTokenSourceWithSkew: tokens are cached and refreshed DefaultExpirySkew (30 seconds) before expiry rather than after it, so a request that starts near the expiry instant never reaches GitHub with an already-expired credential. The window is tunable with WithExpirySkew (application JWTs) and WithInstallationExpirySkew (installation tokens).

Handling rate limits

A throttled request returns a RateLimitError carrying the wait the client computed from GitHub's own headers, so a caller running its own backoff does not have to re-parse a response it never sees:

var rle *githubauth.RateLimitError
if errors.As(err, &rle) {
	time.Sleep(rle.RetryAfter)
}

It unwraps to ErrRateLimited, so errors.Is(err, ErrRateLimited) keeps working. A 403 permission failure is not a rate limit and matches neither, even though GitHub attaches rate-limit headers to it.

Signing with external key stores

NewApplicationTokenSourceFromSigner accepts any RSA-backed crypto.Signer — AWS KMS, Google Cloud KMS, Azure Key Vault, HashiCorp Vault Transit, PKCS#11 HSMs, or ssh-agent — so the App private key never enters process memory. GitHub requires RS256; non-RSA signers are rejected at construction time.

GitHub Enterprise

WithEnterpriseURL targets GitHub Enterprise Server, normalizing the URL the way GHES expects (appending /api/v3/ when needed). WithBaseURL uses the supplied URL verbatim, which fits GitHub Enterprise Cloud with data residency (https://api.SUBDOMAIN.ghe.com/) and httptest servers.

Webhook verification

The webhook subpackage verifies GitHub webhook deliveries (X-Hub-Signature-256, HMAC-SHA256 in constant time) and ships an http.Handler middleware that restores the request body for downstream handlers. See github.com/jferrl/go-githubauth/webhook.

Index

Examples

Constants

View Source
const (
	// DefaultApplicationTokenExpiration is the default expiration time for GitHub App tokens.
	// The maximum allowed expiration is 10 minutes.
	DefaultApplicationTokenExpiration = 10 * time.Minute

	// DefaultExpirySkew is the default early-refresh window applied to cached
	// tokens returned by NewApplicationTokenSource and NewInstallationTokenSource.
	// At 30s the effective validity of a default 10-minute application JWT becomes
	// 8m30s, which is acceptable and eliminates the common in-flight 401 caused
	// by a request starting near exp and arriving at GitHub after exp.
	DefaultExpirySkew = 30 * time.Second
)

Variables

View Source
var ErrRateLimited = errors.New("github API rate limited")

ErrRateLimited wraps errors returned when GitHub has throttled a request: any HTTP 429, or a 403 that GitHub identifies as a rate limit rather than a permission failure. A 403 counts when it carries Retry-After, reports an exhausted budget via X-RateLimit-Remaining: 0, or says so in its message.

A 403 such as "Resource not accessible by integration" is a permission failure and is NOT wrapped, even though GitHub attaches rate-limit headers to it. Callers can branch with errors.Is.

Functions

func NewApplicationTokenSource

func NewApplicationTokenSource[T Identifier](id T, privateKey []byte, opts ...ApplicationTokenOpt) (oauth2.TokenSource, error)

NewApplicationTokenSource creates a GitHub App JWT token source from a PEM-encoded RSA private key. Accepts either int64 App ID or string Client ID. GitHub recommends Client IDs for new apps. Generated JWTs are RS256-signed with iat, exp, and iss claims. JWTs expire in max 10 minutes and include clock drift protection (iat set 60s in past).

The returned token source is wrapped in ReuseTokenSourceWithSkew with DefaultExpirySkew (30s), so cached tokens are refreshed before exp rather than after. With the default 10-minute expiration, which is backdated 60s for clock drift, the effective validity is 8m30s. Override with WithExpirySkew.

For KMS, HSM, Vault, or ssh-agent backed signing, use NewApplicationTokenSourceFromSigner instead — the private key never leaves its secure boundary.

See https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app

Example

Authenticate as a GitHub App with a Client ID (recommended by GitHub for new Apps). The returned source caches the JWT and refreshes it 30s before expiry.

package main

import (
	"fmt"
	"os"

	"github.com/jferrl/go-githubauth"
)

func main() {
	privateKey := []byte(os.Getenv("GITHUB_APP_PRIVATE_KEY"))
	clientID := os.Getenv("GITHUB_APP_CLIENT_ID") // e.g. "Iv1.1234567890abcdef"

	appSource, err := githubauth.NewApplicationTokenSource(clientID, privateKey)
	if err != nil {
		fmt.Println("creating application token source:", err)
		return
	}

	token, err := appSource.Token()
	if err != nil {
		fmt.Println("generating JWT:", err)
		return
	}

	fmt.Println("app JWT:", token.AccessToken)
}
Example (AppID)

Authenticate with a legacy numeric App ID and a custom JWT expiration.

package main

import (
	"fmt"
	"os"
	"strconv"
	"time"

	"github.com/jferrl/go-githubauth"
)

func main() {
	privateKey := []byte(os.Getenv("GITHUB_APP_PRIVATE_KEY"))
	appID, _ := strconv.ParseInt(os.Getenv("GITHUB_APP_ID"), 10, 64)

	appSource, err := githubauth.NewApplicationTokenSource(
		appID,
		privateKey,
		githubauth.WithApplicationTokenExpiration(5*time.Minute),
	)
	if err != nil {
		fmt.Println("creating application token source:", err)
		return
	}

	token, err := appSource.Token()
	if err != nil {
		fmt.Println("generating JWT:", err)
		return
	}

	fmt.Println("app JWT:", token.AccessToken)
}

func NewApplicationTokenSourceFromSigner added in v1.6.0

func NewApplicationTokenSourceFromSigner[T Identifier](id T, signer crypto.Signer, opts ...ApplicationTokenOpt) (oauth2.TokenSource, error)

NewApplicationTokenSourceFromSigner creates a GitHub App JWT token source backed by an external crypto.Signer. Any RSA-backed signer works: AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault Transit, PKCS#11 HSMs, or ssh-agent. The private key never touches process memory.

The signer's public key must be RSA — GitHub requires RS256 (RSASSA-PKCS1-v1_5 with SHA-256, per RFC 7518 §3.3). The signer must return signatures in that form when called with crypto.SHA256; every stdlib-compatible RSA signer does.

See https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app

Example

Sign App JWTs with an external key store. Any RSA-backed crypto.Signer works: AWS KMS, Google Cloud KMS, Azure Key Vault, HashiCorp Vault Transit, PKCS#11 HSMs, or ssh-agent. A local *rsa.PrivateKey stands in for a KMS-backed signer here; in production the private key never touches process memory.

package main

import (
	"crypto"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"os"

	"github.com/jferrl/go-githubauth"
)

func main() {
	clientID := os.Getenv("GITHUB_APP_CLIENT_ID")

	block, _ := pem.Decode([]byte(os.Getenv("GITHUB_APP_PRIVATE_KEY")))
	if block == nil {
		fmt.Println("no PEM block found")
		return
	}
	key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		fmt.Println("parsing private key:", err)
		return
	}
	var signer crypto.Signer = key // swap for a KMS/HSM/Vault-backed signer

	appSource, err := githubauth.NewApplicationTokenSourceFromSigner(clientID, signer)
	if err != nil {
		fmt.Println("creating application token source:", err)
		return
	}

	token, err := appSource.Token()
	if err != nil {
		fmt.Println("generating JWT:", err)
		return
	}

	fmt.Println("app JWT:", token.AccessToken)
}

func NewInstallationTokenSource

func NewInstallationTokenSource(id int64, src oauth2.TokenSource, opts ...InstallationTokenSourceOpt) oauth2.TokenSource

NewInstallationTokenSource creates a GitHub App installation token source. Requires installation ID and a GitHub App JWT token source for authentication.

A non-positive installation ID is a configuration error, reported by the first call to Token() rather than sent to GitHub as a request that can only 404.

The returned token source is wrapped in ReuseTokenSourceWithSkew so cached tokens are refreshed DefaultExpirySkew before their expiry, eliminating in-flight 401s when a request starts close to exp and reaches GitHub after. Override the window with WithInstallationExpirySkew.

See https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app

Example

The full GitHub App chain: App JWT -> installation token -> authenticated HTTP client. The client works standalone or can be passed to github.NewClient from google/go-github.

package main

import (
	"context"
	"fmt"
	"os"
	"strconv"

	"github.com/jferrl/go-githubauth"
	"golang.org/x/oauth2"
)

func main() {
	privateKey := []byte(os.Getenv("GITHUB_APP_PRIVATE_KEY"))
	clientID := os.Getenv("GITHUB_APP_CLIENT_ID")
	installationID, _ := strconv.ParseInt(os.Getenv("GITHUB_INSTALLATION_ID"), 10, 64)

	appSource, err := githubauth.NewApplicationTokenSource(clientID, privateKey)
	if err != nil {
		fmt.Println("creating application token source:", err)
		return
	}

	installationSource := githubauth.NewInstallationTokenSource(installationID, appSource)

	// Every request carries a valid installation token; refresh is automatic.
	httpClient := oauth2.NewClient(context.Background(), installationSource)

	resp, err := httpClient.Get("https://api.github.com/installation/repositories")
	if err != nil {
		fmt.Println("listing installation repositories:", err)
		return
	}
	defer func() { _ = resp.Body.Close() }()

	fmt.Println("status:", resp.Status)
}
Example (Enterprise)

Point the installation token source at GitHub Enterprise Server (GHES) or GitHub Enterprise Cloud (GHEC) with data residency.

package main

import (
	"fmt"
	"os"
	"strconv"

	"github.com/jferrl/go-githubauth"
)

func main() {
	privateKey := []byte(os.Getenv("GITHUB_APP_PRIVATE_KEY"))
	clientID := os.Getenv("GITHUB_APP_CLIENT_ID")
	installationID, _ := strconv.ParseInt(os.Getenv("GITHUB_INSTALLATION_ID"), 10, 64)

	appSource, err := githubauth.NewApplicationTokenSource(clientID, privateKey)
	if err != nil {
		fmt.Println("creating application token source:", err)
		return
	}

	// GHES: the URL is normalized the way GHES expects (/api/v3/ appended).
	ghes := githubauth.NewInstallationTokenSource(
		installationID,
		appSource,
		githubauth.WithEnterpriseURL("https://github.example.com"),
	)

	// GHEC with data residency: the URL is used verbatim.
	ghec := githubauth.NewInstallationTokenSource(
		installationID,
		appSource,
		githubauth.WithBaseURL("https://api.octocorp.ghe.com"),
	)

	_, _ = ghes, ghec
}

func NewPersonalAccessTokenSource added in v1.4.0

func NewPersonalAccessTokenSource(token string) oauth2.TokenSource

NewPersonalAccessTokenSource creates a token source for GitHub personal access tokens. The provided token should be a valid GitHub personal access token (classic or fine-grained). This token source returns the same token value for all Token() calls without expiration, making it suitable for long-lived authentication scenarios.

Example

Authenticate with a classic or fine-grained personal access token.

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/jferrl/go-githubauth"
	"golang.org/x/oauth2"
)

func main() {
	token := os.Getenv("GITHUB_TOKEN") // "ghp_..." or "github_pat_..."

	tokenSource := githubauth.NewPersonalAccessTokenSource(token)
	httpClient := oauth2.NewClient(context.Background(), tokenSource)

	resp, err := httpClient.Get("https://api.github.com/user")
	if err != nil {
		fmt.Println("getting user:", err)
		return
	}
	defer func() { _ = resp.Body.Close() }()

	fmt.Println("status:", resp.Status)
}

func Ptr added in v1.5.0

func Ptr[T any](v T) *T

Ptr is a helper function to create a pointer to a value. This is useful when constructing InstallationTokenOptions with permissions.

func ReuseTokenSourceWithSkew added in v1.6.0

func ReuseTokenSourceWithSkew(t *oauth2.Token, src oauth2.TokenSource, skew time.Duration) oauth2.TokenSource

ReuseTokenSourceWithSkew wraps src so cached tokens are refreshed proactively, skew before their expiry. oauth2.ReuseTokenSource refreshes only once exp has passed (via oauth2.Token.Valid), so a request that starts at T-100ms with a token expiring at T can arrive at GitHub already expired and yield a 401 the caller must manually retry. This wrapper refreshes when time.Until(t.Expiry) <= skew, cutting out that race.

If skew is zero or negative the wrapper delegates to oauth2.ReuseTokenSource, preserving its exact behavior. An initial non-nil t is used until it needs refresh under the same rule. The returned source is safe for concurrent use; concurrent Token calls that find the cache stale collapse into a single upstream fetch.

Example

Wrap any third-party oauth2.TokenSource so cached tokens refresh before expiry instead of after it.

package main

import (
	"time"

	"github.com/jferrl/go-githubauth"
	"golang.org/x/oauth2"
)

func main() {
	var upstream oauth2.TokenSource // any token source, e.g. from another provider

	src := githubauth.ReuseTokenSourceWithSkew(nil, upstream, 30*time.Second)
	_ = src
}

Types

type ApplicationTokenOpt

type ApplicationTokenOpt func(*applicationTokenSource)

ApplicationTokenOpt is a functional option for configuring an applicationTokenSource.

func WithApplicationTokenExpiration

func WithApplicationTokenExpiration(exp time.Duration) ApplicationTokenOpt

WithApplicationTokenExpiration sets the JWT expiration duration. Must be greater than 90 seconds and at most 10 minutes. Ten minutes is GitHub's ceiling. The lower bound is the 60 second clock-drift backdate plus DefaultExpirySkew: at or below the backdate the JWT is already expired when minted, and within the skew above it the cache never holds the token, so every call re-signs — a remote round trip for a KMS, HSM or Vault signer. Invalid values default to 10 minutes. See https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app#about-json-web-tokens-jwts

func WithExpirySkew added in v1.6.0

func WithExpirySkew(d time.Duration) ApplicationTokenOpt

WithExpirySkew overrides the default early-refresh window (DefaultExpirySkew, 30s) applied to the token cache returned by NewApplicationTokenSource. The cached token is refreshed when time.Until(exp) <= d. A zero or negative value disables the skew and falls back to oauth2.ReuseTokenSource behavior (refresh only after exp has passed).

Tune this when your application token expiration (see WithApplicationTokenExpiration) is short: the effective validity is expiration - 60s of clock-drift backdating - skew, so with the default 10-minute expiration and 30s skew tokens are refreshed at 8m30s.

type Identifier added in v1.3.0

type Identifier interface {
	~int64 | ~string
}

Identifier constrains GitHub App identifiers to int64 (App ID) or string (Client ID).

type InstallationPermissions added in v1.5.0

type InstallationPermissions struct {
	Actions                         *string `json:"actions,omitempty"`
	Administration                  *string `json:"administration,omitempty"`
	Checks                          *string `json:"checks,omitempty"`
	Contents                        *string `json:"contents,omitempty"`
	ContentReferences               *string `json:"content_references,omitempty"`
	Deployments                     *string `json:"deployments,omitempty"`
	Environments                    *string `json:"environments,omitempty"`
	Issues                          *string `json:"issues,omitempty"`
	Metadata                        *string `json:"metadata,omitempty"`
	Packages                        *string `json:"packages,omitempty"`
	Pages                           *string `json:"pages,omitempty"`
	PullRequests                    *string `json:"pull_requests,omitempty"`
	RepositoryAnnouncementBanners   *string `json:"repository_announcement_banners,omitempty"`
	RepositoryHooks                 *string `json:"repository_hooks,omitempty"`
	RepositoryProjects              *string `json:"repository_projects,omitempty"`
	SecretScanningAlerts            *string `json:"secret_scanning_alerts,omitempty"`
	Secrets                         *string `json:"secrets,omitempty"`
	SecurityEvents                  *string `json:"security_events,omitempty"`
	SingleFile                      *string `json:"single_file,omitempty"`
	Statuses                        *string `json:"statuses,omitempty"`
	VulnerabilityAlerts             *string `json:"vulnerability_alerts,omitempty"`
	Workflows                       *string `json:"workflows,omitempty"`
	Members                         *string `json:"members,omitempty"`
	OrganizationAdministration      *string `json:"organization_administration,omitempty"`
	OrganizationCustomRoles         *string `json:"organization_custom_roles,omitempty"`
	OrganizationAnnouncementBanners *string `json:"organization_announcement_banners,omitempty"`
	OrganizationHooks               *string `json:"organization_hooks,omitempty"`
	OrganizationPlan                *string `json:"organization_plan,omitempty"`
	OrganizationProjects            *string `json:"organization_projects,omitempty"`
	OrganizationPackages            *string `json:"organization_packages,omitempty"`
	OrganizationSecrets             *string `json:"organization_secrets,omitempty"`
	OrganizationSelfHostedRunners   *string `json:"organization_self_hosted_runners,omitempty"`
	OrganizationUserBlocking        *string `json:"organization_user_blocking,omitempty"`
	TeamDiscussions                 *string `json:"team_discussions,omitempty"`
}

InstallationPermissions represents the permissions granted to an installation token.

type InstallationToken added in v1.5.0

type InstallationToken struct {
	Token        string                   `json:"token"`
	ExpiresAt    time.Time                `json:"expires_at"`
	Permissions  *InstallationPermissions `json:"permissions,omitempty"`
	Repositories []Repository             `json:"repositories,omitempty"`
}

InstallationToken represents a GitHub App installation token.

type InstallationTokenOptions added in v1.5.0

type InstallationTokenOptions struct {
	// Repositories is a list of repository names that the token should have access to.
	Repositories []string `json:"repositories,omitempty"`
	// RepositoryIDs is a list of repository IDs that the token should have access to.
	RepositoryIDs []int64 `json:"repository_ids,omitempty"`
	// Permissions are the permissions granted to the access token.
	Permissions *InstallationPermissions `json:"permissions,omitempty"`
}

InstallationTokenOptions specifies options for creating an installation token.

type InstallationTokenSourceOpt

type InstallationTokenSourceOpt func(*installationTokenSource)

InstallationTokenSourceOpt is a functional option for InstallationTokenSource.

func WithBaseURL added in v1.7.0

func WithBaseURL(baseURL string) InstallationTokenSourceOpt

WithBaseURL sets the API base URL used to create installation tokens, closely mirroring how go-github lets callers point the client at a custom endpoint. Unlike WithEnterpriseURL, the URL is used verbatim — only a trailing slash is appended when missing, and no "/api/v3/" suffix is added.

Use this for:

Option order does not matter; it may be combined with WithHTTPClient, WithEnterpriseURL, and WithRetryOnThrottle in any order. If the URL cannot be parsed, the error is reported by the first call to Token() rather than silently falling back to the public GitHub API.

func WithContext added in v1.1.0

WithContext sets the context for the GitHub App installation token source.

func WithEnterpriseURL added in v1.5.0

func WithEnterpriseURL(baseURL string) InstallationTokenSourceOpt

WithEnterpriseURL sets the base URL for GitHub Enterprise Server (GHES). The URL is normalized the way GHES expects, appending the "/api/v3/" path when the host is not already an "api." subdomain. For GitHub Enterprise Cloud or a verbatim URL (such as an httptest server), use WithBaseURL instead.

Option order does not matter; it may be combined with WithHTTPClient, WithBaseURL, and WithRetryOnThrottle in any order. If the URL cannot be parsed, the error is reported by the first call to Token() rather than silently falling back to the public GitHub API.

func WithHTTPClient

func WithHTTPClient(client *http.Client) InstallationTokenSourceOpt

WithHTTPClient sets the HTTP client used to call the GitHub API. Its transport is wrapped so installation-token requests are authenticated with the GitHub App JWT; any base URL configured via WithBaseURL or WithEnterpriseURL is preserved, so these options may be combined in any order.

A nil client is a configuration error reported by the first call to Token().

func WithInstallationExpirySkew added in v1.6.0

func WithInstallationExpirySkew(d time.Duration) InstallationTokenSourceOpt

WithInstallationExpirySkew overrides the default early-refresh window (DefaultExpirySkew, 30s) applied to the installation token cache returned by NewInstallationTokenSource. Installation tokens live 1 hour, so the 30s default leaves ~59m30s effective validity — this option exists mostly for parity with WithExpirySkew. A zero or negative value falls back to oauth2.ReuseTokenSource behavior.

func WithInstallationTokenOptions

func WithInstallationTokenOptions(opts *InstallationTokenOptions) InstallationTokenSourceOpt

WithInstallationTokenOptions sets the options for the GitHub App installation token.

func WithRetryOnThrottle added in v1.6.0

func WithRetryOnThrottle(enabled bool) InstallationTokenSourceOpt

WithRetryOnThrottle enables or disables a single automatic retry when GitHub throttles the installation token POST. Enabled by default.

Any 429 counts as throttled. A 403 counts only when GitHub identifies it as a rate limit — it carries Retry-After, reports an exhausted budget via X-RateLimit-Remaining: 0, or says so in its message. A permission failure such as "Resource not accessible by integration" is terminal and is retried by neither setting, despite carrying rate-limit headers.

On a throttled response the client sleeps the duration hinted by Retry-After or x-ratelimit-reset, falling back to one minute for a hintless rate-limit 403 (capped at 60s, honoring ctx cancellation), and retries once. Subsequent failures bubble up unchanged. On a terminal throttle the returned error wraps ErrRateLimited so callers can branch with errors.Is.

Disable this when the caller implements its own backoff or when deterministic latency matters more than transient rate-limit resilience.

type RateLimitError added in v1.8.0

type RateLimitError struct {
	// StatusCode is the HTTP status GitHub returned, 429 or 403.
	StatusCode int

	// RetryAfter is how long to wait before retrying, taken from Retry-After or
	// X-RateLimit-Reset and capped at maxRetrySleep. It falls back to a
	// documented default when GitHub sends no usable hint. It is zero when
	// GitHub says to retry immediately, or when the reset instant has already
	// passed, so callers must treat zero as "retry now" rather than "no hint".
	RetryAfter time.Duration

	// Message is the response body, truncated to maxErrorBodyBytes.
	Message string
}

RateLimitError is the concrete error returned when GitHub throttles a request. It carries the wait the client computed from GitHub's own hints, so a caller running its own backoff does not have to re-parse the headers the client already read:

var rle *githubauth.RateLimitError
if errors.As(err, &rle) {
	time.Sleep(rle.RetryAfter)
}

It unwraps to ErrRateLimited, so errors.Is(err, ErrRateLimited) keeps working.

func (*RateLimitError) Error added in v1.8.0

func (e *RateLimitError) Error() string

func (*RateLimitError) Unwrap added in v1.8.0

func (e *RateLimitError) Unwrap() error

Unwrap reports ErrRateLimited so callers can branch with errors.Is without knowing about this type.

type Repository added in v1.5.0

type Repository struct {
	ID   *int64  `json:"id,omitempty"`
	Name *string `json:"name,omitempty"`
}

Repository represents a GitHub repository.

Directories

Path Synopsis
cmd
githubauth command
Command githubauth mints GitHub App credentials from the command line.
Command githubauth mints GitHub App credentials from the command line.
Package webhook verifies GitHub webhook deliveries.
Package webhook verifies GitHub webhook deliveries.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL