Skip to content

Git Authentication

Git operations on the estate authenticate to GitHub through one of two mechanisms: an SSH key for repository transport (clone, push, pull), and a fine-grained personal access token for the GitHub API (the gh CLI, automation). This page explains both, the rules for choosing between them, and how to verify authentication on any machine.

Authentication answers two different questions:

  1. Transport — who may clone/push/pull a repository over the wire?
  2. API — which operations may scripts perform through the GitHub REST API (opening pull requests, triaging issues, reading repository metadata)?

The two answers are deliberately different mechanisms:

Question Mechanism Where it lives Expiry
Transport SSH key pair ~/.ssh/id_ed25519 + the public key registered on the GitHub account none
API Fine-grained personal access token (PAT) gh CLI (~/.config/gh/hosts.yml) or an environment variable optional, set at creation

An SSH key pair is a private key (~/.ssh/id_ed25519) and a public key (~/.ssh/id_ed25519.pub). The public key is registered on the GitHub account (Settings → SSH and GPG keys); the private key stays on the machine. During a git push, the client proves possession of the private key; GitHub never sees the key material.

Why SSH is the standard for transport:

  • No expiry — a key works until it is removed from the account.
  • No secret in the repository — the remote URL is git@github.com:<owner>/<repo>.git; authentication happens out of band.
  • One credential per machine — every repository on the same host uses the same key.

Verify a key:

Terminal window
ssh -T git@github.com
# Hi <user>! You've successfully authenticated, but GitHub does not provide shell access.

A fine-grained personal access token (Settings → Developer settings → Fine-grained tokens) grants scoped, per-repository permissions (for example Contents: Read and write on a single repository) and can carry an expiry date. It is the recommended token kind for the API; classic tokens are deprecated for new use.

Fine-grained tokens are valid for:

  • the gh CLI (gh auth login --with-token),
  • API calls in automation (GH_TOKEN/GITHUB_TOKEN),
  • HTTPS git transport as a fallback (through a credential helper).

They are NOT meant to be embedded in remote URLs — see below.

Verify a token only when it is needed, and never print it:

Terminal window
GH_TOKEN="$TOKEN" gh api user --jq .login # prints the account login
Use case Mechanism Example
Clone / push / pull SSH key git clone git@github.com:owner/repo.git
gh CLI (PRs, issues, review) Fine-grained PAT gh pr view 12
Automation / CI scripts Fine-grained PAT GH_TOKEN=... gh api ...
HTTPS fallback transport PAT through credential helper gh auth setup-git

The rule: credentials never belong in a remote URL

Section titled “The rule: credentials never belong in a remote URL”

Embedding a token in a clone URL (https://x-access-token:<TOKEN.....) works at first, then leaks the secret through:

  • git remote -v and git config --list on the machine,
  • shell history and terminal logs,
  • backup archives and error reports,
  • any repository or CI log where the URL is echoed.

A leaked token is compromised even if the repository itself is private: revoke it immediately in Developer settings and never re-embed it. Convert an existing token-in-URL remote with:

Terminal window
git remote set-url origin git@github.com:<owner>/<repo>.git
git remote -v # verify: no token visible
Check Command Expected
SSH key ssh -T git@github.com Hi <user>! You've successfully authenticated
Transport, any repo git ls-remote origin HEAD (in a repo) exit 0, one line <sha> HEAD
No token in URLs git remote -v every remote starts with git@ or plain https://
gh API gh api user --jq .login the account login
HTTPS fallback git ls-remote https://github.com/<owner>/<repo>.git HEAD exit 0