Personal Access Tokens

A Personal Access Token (PAT) is a long, random string that GitHub lets you generate as a stand-in for your password. Since 2021, GitHub no longer accepts your account password for Git operations over HTTPS or for its API, so a PAT (or an SSH key) is how your command-line tools prove who you are. Understanding how PATs are scoped, stored, and rotated is essential for using GitHub securely from the terminal, from CI systems, and from the gh command-line tool.

Overview / How it works

Git itself has no idea what a "GitHub account" is. When you clone, fetch, or push over HTTPS, Git simply needs credentials for that URL, and it hands the job of obtaining them to a credential helper. On GitHub’s side, a Personal Access Token is a bearer credential: whoever presents a valid token over HTTPS or in an Authorization header is treated as the account that created it, restricted to whatever permissions the token was granted. Unlike your account password, a PAT can be scoped narrowly, given an expiration date, and revoked individually without touching your login password or other tokens.

Classic vs fine-grained tokens

GitHub currently offers two kinds of PAT. Classic tokens are simple opaque strings (prefixed ghp_) authorized with broad OAuth-style scopes such as repo, workflow, or read:org — a repo scope grants access to every repository you can see, public and private. Fine-grained tokens (prefixed github_pat_) are the modern replacement: you pick exactly which repositories the token can touch (one repo, a handful, or all repos in an account) and exactly which permissions it has on them (e.g. Contents: Read and write, Pull requests: Read-only), each with an independent expiration date enforced by GitHub’s servers. Fine-grained tokens dramatically shrink the blast radius if a token ever leaks, because a compromised token for one small repo cannot touch your other private repositories.

Where tokens are created

Both token types are created on GitHub’s website, not from the command line: Settings → Developer settings → Personal access tokens, choosing either "Fine-grained tokens" or "Tokens (classic)." GitHub shows you the token exactly once at creation time — if you navigate away without copying it, you must generate a new one. Treat the token like a password: it is not tied to two-factor authentication the way your login is, so anyone holding a valid PAT can act as you within its scope.

Syntax

There is no single "PAT command" — a token is a value you supply wherever Git or the GitHub API asks for authentication. The two places you’ll use it most are Git’s HTTPS authentication prompt and the gh CLI’s login command.

Context How the PAT is used
Git over HTTPS Typed as the password when Git prompts "Password for https://…" (your GitHub username, or any string, goes in the username field)
gh CLI gh auth login interactively, or gh auth login --with-token reading a token from stdin
REST/GraphQL API Sent as an HTTP header, e.g. Authorization: Bearer <token>
CI systems (GitHub Actions, etc.) Stored as an encrypted repository/organization secret, injected as an environment variable at run time — never pasted into the workflow file

Examples

Example 1: Cloning a private repository over HTTPS

git clone https://github.com/octocat/secret-project.git

Output:

Cloning into 'secret-project'...
Username for 'https://github.com': octocat
Password for 'https://octocat@github.com':
remote: Enumerating objects: 210, done.
remote: Counting objects: 100% (210/210), done.
remote: Compressing objects: 100% (150/150), done.
remote: Total 210 (delta 40), reused 200 (delta 35), pack-reused 0
Receiving objects: 100% (210/210), 128.40 KiB | 3.20 MiB/s, done.
Resolving deltas: 100% (40/40), done.

Git prompts for a username and password. You type your GitHub username, then paste your PAT (not your account password) at the password prompt — the terminal shows nothing as you type it, which is normal. If the token is valid and has permission to read the repository, the clone proceeds.

Example 2: Caching a token so you aren’t prompted every time

git config --global credential.helper cache
git config --global credential.helper "cache --timeout=3600"

Output: no output on success.

The cache helper holds your credentials in memory (never on disk) for a limited time — 15 minutes by default, or 3600 seconds (1 hour) with the second command. After the timeout expires, Git asks for the token again on the next HTTPS operation.

Example 3: Persisting a token with the store helper

git config --global credential.helper store

Output: no output on success.

The next time you authenticate, Git writes your username and token in plaintext to ~/.git-credentials:

https://octocat:ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@github.com

Every future HTTPS request to github.com reuses this line automatically, with no prompt at all. Convenient, but anyone who can read that file — another user on a shared machine, malware, a stolen laptop disk — can act as you on GitHub within the token’s scope. Prefer your OS’s credential manager (Git for Windows, macOS Keychain via credential.helper osxkeychain, or libsecret on Linux) when it’s available, since those encrypt the token at rest.

Example 4: Authenticating the gh CLI with a token

echo "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | gh auth login --with-token
gh auth status

Output:

github.com
  ✓ Logged in to github.com as octocat (keyring)
  ✓ Git operations for github.com configured to use https protocol.
  ✓ Token: ghp_****************************
  ✓ Token scopes: 'repo', 'read:org', 'workflow'

gh auth login --with-token reads a token from standard input, which is useful for scripting or CI setup without an interactive prompt. gh also configures Git itself to use the same token as its credential helper, so ordinary git push/git pull commands start working without a separate setup step. gh auth status confirms who you’re logged in as and which scopes the active token carries.

How it works step by step

  1. Git sees an HTTPS remote URL (e.g. https://github.com/octocat/secret-project.git) and determines the operation needs authentication.
  2. Git calls the configured credential.helper (cache, store, keychain, or the gh-managed helper) asking "do you have credentials for github.com?"
  3. If the helper has none cached, Git falls back to prompting you directly on the terminal for a username and password/token.
  4. Git sends the username and PAT to GitHub over HTTPS as Basic authentication for the request (e.g. the Git upload-pack/receive-pack endpoints).
  5. GitHub’s servers look up the token, check it hasn’t expired or been revoked, and verify it carries a scope/permission that covers the requested operation and repository.
  6. If everything checks out, GitHub performs the operation (serves objects for a clone/fetch, accepts pushed objects) and returns a normal Git response; Git then optionally hands the successful credentials back to the helper to store for next time.

The Git object model itself — commits, trees, blobs, branch pointers — is unaffected by any of this. A PAT only governs the transport-layer question of "is this request allowed to read/write this repository," not what gets written into your local .git directory.

Common Mistakes

Mistake 1: Embedding the token directly in the remote URL

git remote set-url origin https://ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@github.com/octocat/secret-project.git

This works, but the token now sits in plaintext inside .git/config and typically also lands in your shell history file. Anyone with read access to either can push or pull as you. Fix: use a credential helper (cache, your OS keychain, or gh‘s built-in helper) instead of writing the token into the URL, and immediately revoke any token that was exposed this way.

Mistake 2: Granting a classic token every scope "just in case"

Checking every box on a classic token (repo, admin:org, delete_repo, etc.) means a single leaked token can read, write, or delete anything across your account. Fix: use a fine-grained token limited to the one or two repositories you’re actually working with, with only the specific permissions the task needs (often just Contents: Read and write).

Mistake 3: Never setting an expiration or rotating tokens

A token created years ago with no expiration is a standing liability — if it ever leaks, it stays valid indefinitely. Fix: always set an expiration (GitHub defaults fine-grained tokens to 30 days but lets you extend it), calendar-remind yourself to rotate long-lived tokens, and delete tokens you no longer use from Settings → Developer settings.

Mistake 4: Committing a token into the repository

Pasting a token into a .env file, a config file, or a script and committing it makes the token part of your permanent Git history, retrievable even after you delete it in a later commit. Fix: keep secrets out of tracked files (add them to .gitignore), use GitHub Actions/organization secrets for CI, and if a token is ever committed, revoke it on GitHub immediately — rewriting history afterward does not undo the exposure since the token must be treated as compromised the moment it’s pushed.

Best Practices

  • Prefer fine-grained tokens scoped to specific repositories and the minimum permissions required, over classic tokens with broad scopes.
  • Always set an expiration date, and prefer short ones (30–90 days) for anything used interactively.
  • Store tokens in an encrypted credential manager (OS keychain) rather than credential.helper store‘s plaintext file when your platform supports it.
  • Use a separate token per machine or purpose (laptop, CI job, script) so you can revoke one without breaking the others.
  • For everyday day-to-day development on your own machine, consider an SSH key instead of a PAT — it avoids re-authenticating and doesn’t expire unless you rotate it yourself.
  • In CI/CD (GitHub Actions), rely on the automatically provided GITHUB_TOKEN or an encrypted repository secret — never hardcode a PAT into a workflow YAML file.
  • Revoke a token immediately from Settings → Developer settings the moment you suspect it has leaked; this invalidates it everywhere at once.
  • Audit your token list periodically and delete anything you no longer recognize or use.

Practice Exercises

  • Create a fine-grained PAT scoped to a single test repository with "Contents: Read and write" permission and a 7-day expiration. Use it to clone that repository over HTTPS, typing the token as the password.
  • Set credential.helper to cache --timeout=60, run an authenticated Git command, then wait over a minute and run another one. Confirm you’re prompted again once the cache expires, and explain in your own words why the second prompt happened.
  • Generate a throwaway PAT, use gh auth login --with-token to authenticate the gh CLI with it, then go to GitHub’s website and revoke that token. Run gh auth status again and observe what happens — this simulates responding to a leaked token.

Summary

  • GitHub requires a Personal Access Token (or SSH key) instead of your account password for Git-over-HTTPS and API access.
  • Fine-grained tokens (github_pat_…) scope access to specific repositories and permissions with server-enforced expiration; classic tokens (ghp_…) use broader, account-wide scopes.
  • Tokens are created on GitHub’s website under Settings → Developer settings and shown only once at creation.
  • Git obtains a PAT through its credential prompt or a credential helper, then sends it to GitHub over HTTPS to authenticate each operation.
  • Avoid embedding tokens in remote URLs or committing them to a repository; prefer credential managers, minimal scopes, and short expirations.
  • Revoke and rotate a token immediately if it is ever exposed — this doesn’t affect your account password or other tokens.