Personal Access Tokens (PATs)
A Personal Access Token is a long random string GitHub generates on your behalf that you use instead of your password for anything that goes through HTTPS or the REST API. GitHub removed password auth for Git operations back in August 2021, so if you push over HTTPS you are using a PAT (or an SSH key — see the previous page). They expire, they have explicit scopes, and you can revoke them individually.
Why PATs exist
Your account password is too dangerous to type into scripts and CI runners — it unlocks everything.
PATs are revocable: leak one, delete it, generate a new one. The password stays safe.
PATs have scopes: a token that can only read repos cannot be used to delete them.
They have expiry dates: stolen tokens become useless after the deadline.
They show up in audit logs — you can see exactly which token did what.
Two flavours: classic vs fine-grained
Classic PAT | Fine-grained PAT | |
|---|---|---|
Scope model | Coarse: repo, workflow, packages... | Per-repository, per-permission |
Org control | Org owners cannot disallow | Org owners can require approval |
Expiration | Optional (you can pick "never") | Required, max 1 year |
UI label | Tokens (classic) | Fine-grained tokens |
Use when | Older tools, GHE Server compatibility | New work — recommended |
Status | Will be deprecated eventually | Preferred going forward |
Creating a token, click by click
Go to github.com → Settings → Developer settings → Personal access tokens.
Pick Fine-grained tokens (recommended) or Tokens (classic).
Click Generate new token. Re-authenticate with 2FA if asked.
Name it after where it will live:
laptop-https-push,ci-deploy-staging,dependabot-bot.Set an expiration date. Pick the shortest you can tolerate. 30 or 90 days is a good default.
For fine-grained: pick a Resource owner (you or an org), then choose specific repos.
Tick only the permissions you actually need. Least privilege is the rule.
Click Generate. Copy the token immediately — once you leave the page it is gone forever.
Picking scopes — the least-privilege checklist
Goal | Classic scope | Fine-grained permission |
|---|---|---|
Clone/pull public repos only | public_repo | Contents: Read (public) |
Clone/push private repos | repo | Contents: Read & Write |
Use the gh CLI normally | repo, read:org, workflow | Contents R/W, Metadata R, Workflows R/W |
Trigger workflows / read CI | workflow | Actions: Read & Write |
Publish to GitHub Packages | write:packages | Packages: Read & Write |
Manage repo settings | admin:repo_hook, admin:org | Administration: Read & Write |
Pause before each checkbox: do I really need write here? Do I really need org admin? Most day-to-day tokens need almost nothing.
Using a PAT to push
The first HTTPS push prompts you
git push # Username for 'https://github.com': you # Password for 'https://you@github.com': <paste the PAT here> # (your real account password will NOT work)
Storing PATs in a credential helper
Typing a 90-character random string for every push is painful. Git's credential helpers cache the PAT securely in your OS keychain so subsequent pushes are silent.
One-time setup per OS
# macOS — uses the system keychain git config --global credential.helper osxkeychain # Windows — Git for Windows ships Git Credential Manager git config --global credential.helper manager # Linux with libsecret (GNOME) — best option sudo apt install libsecret-1-0 libsecret-1-dev sudo make --directory=/usr/share/doc/git/contrib/credential/libsecret git config --global credential.helper /usr/share/doc/git/contrib/credential/libsecret/git-credential-libsecret # Fallback (plain-text storage — only on trusted machines!) git config --global credential.helper store
After setup, your first push caches the PAT
git push # Username: you # Password: <paste PAT once> # Subsequent pushes are silent.
Using gh to handle PATs for you
The easy button
gh auth login # ? Where do you use GitHub? -> GitHub.com # ? Preferred protocol for Git -> HTTPS # ? Authenticate -> Login with a web browser # (browser flow generates a PAT and stores it for you) # After this, both gh and git use the same token transparently. git push # silent
Rotating a PAT
A small ritual you should do every quarter
# 1. Generate a new PAT in Settings -> Developer settings. # 2. Update credential helper: git credential-osxkeychain erase <<EOF host=github.com protocol=https EOF # 3. Next push prompts; paste the new token. git push # 4. Delete the old token in Settings (don't just let it expire).
Where PATs go wrong
Committed to a repo by accident — anyone who clones can see it. GitHub scans for leaked tokens and revokes them, but assume it leaked the moment you pushed.
Sent in a screenshot or chat — same thing. Revoke it now.
Stored in CI as a plaintext env var visible in logs — use secrets, never echo it.
Used with too many scopes — when stolen, attacker can do more damage. Always least-privilege.
No expiration — stays valid until you remember to delete it. Always set an expiry.
Detecting and recovering from a leak
Scan your repo history for accidental tokens
# Tools like trufflehog, gitleaks, or detect-secrets brew install trufflehog trufflehog git file://. --only-verified # If you find one: # 1. Go to Settings -> Developer settings -> revoke the token NOW. # 2. Rotate. Don't rewrite history hoping no one cloned — assume they did. # 3. Add a pre-commit hook so it can't happen again.
SSH vs PAT — when to use which
Scenario | Recommended |
|---|---|
Daily Git pushes from your laptop | SSH key |
CI runner that builds and pushes | Fine-grained PAT (least scopes) |
Calling the REST API from a script | Fine-grained PAT |
Corporate network blocks port 22 | PAT over HTTPS |
Bot account that opens PRs | Fine-grained PAT or GitHub App |