Aller au contenu

Authenticating with personal access tokens

The homelab repository is private. On the website, Sam is signed in, so GitHub knows who is asking. In the terminal, Git must prove Sam's identity on every clone, fetch, pull, and push. Since 2021, the GitHub password no longer works for that.

The password is refused

Sam clones the repository on a new laptop, and types the GitHub password when Git asks for it:

$ git clone https://github.com/sam-rivera/homelab.git
Cloning into 'homelab'...
Username for 'https://github.com': sam-rivera
Password for 'https://[email protected]':
remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/sam-rivera/homelab.git/'

The password of a GitHub account is only for signing in to the website. Git operations need another kind of credential:

Method URL type How it works
Personal access token (PAT) HTTPS A generated string, typed instead of the password
SSH key SSH ([email protected]:...) A key pair: the public key is registered on GitHub, the private key stays on your computer
GitHub CLI HTTPS gh auth login signs in through the browser and configures Git
Git Credential Manager HTTPS A credential helper that signs in through the browser and stores the credentials securely

The last two create and store a token for you. This page covers personal access tokens, which are what the others use behind the scenes, and the simplest to understand.

What is a personal access token?

A personal access token (PAT) is a long random string that GitHub generates for your account. It replaces the password for Git over HTTPS, and for the GitHub API. It is more secure than a password:

  • it can be limited to some repositories and some permissions (for example, read and write the code of one repository, nothing else);
  • it expires;
  • it can be revoked at any time, without changing your password;
  • it cannot be used to sign in to the website or change your account settings.

A token is only needed outside the browser: on github.com, your normal sign-in is enough.

GitHub has two kinds of tokens:

Fine-grained tokens (recommended) Tokens (classic)
Access Selected repositories of one owner (your account or one organization) Every repository you can access
Permissions Precise: for example Contents: read and write, Issues: read only Broad scopes: repo gives full control of all your repositories
Expiration Required by default, with custom dates Optional (but strongly recommended)
Prefix github_pat_ ghp_
When to use By default When a fine-grained token cannot do the job, for example to contribute to repositories of several owners

Creating a fine-grained token

  1. Click your avatar (top right) > Settings.
  2. At the bottom of the side menu, click Developer settings.
  3. Click Personal access tokens > Fine-grained tokens, then Generate new token.
  4. Fill in the form:

    Field Sam's value Why
    Token name homelab-laptop Says where the token is used, to find it later
    Expiration 30 days A leaked token stops working by itself
    Resource owner sam-rivera The account that owns the repositories
    Repository access Only select repositories: sam-rivera/homelab No access to other repositories
    Permissions > Repository permissions Contents: Read and write Enough to clone, pull, and push. Metadata: read-only is added automatically
  5. Click Generate token.

  6. Copy the token now. GitHub displays it only once: after you leave the page, you can never see it again. If it is lost, generate a new one.

For a classic token, the path is Personal access tokens > Tokens (classic) > Generate new token (classic): a Note (its name), an Expiration, and scopes, where repo is the one needed for private repositories.

A token is a password

Anyone who has the token can do everything it allows, as you. Never share it, never paste it in an issue, a chat, or a screenshot, and never commit it. If GitHub finds one of its tokens in a public repository, it revokes it automatically; for any other leak, delete it yourself immediately on the token page.

Using the token

When Git asks for a password, paste the token instead. Nothing is displayed while you type or paste: this is normal.

$ git clone https://github.com/sam-rivera/homelab.git
Cloning into 'homelab'...
Username for 'https://github.com': sam-rivera
Password for 'https://[email protected]':
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (3/3), done.
$ ls
homelab

Public repositories can be cloned and pulled without any credentials; pushing always requires them.

Not typing it every time: credential helpers

Without configuration, Git asks for the username and token at every operation. A credential helper remembers them:

Helper Command Where the token is kept
cache git config --global credential.helper 'cache --timeout=3600' In memory, for one hour (15 minutes by default)
libsecret (Linux desktops) git config --global credential.helper libsecret (if the helper is installed) In the desktop keyring, encrypted
Git Credential Manager Installed separately, configures itself In the system's secure storage
store git config --global credential.helper store In ~/.git-credentials, in plain text
  • git credential-cache exit makes the cache forget everything immediately.
  • With store, anyone who can read your home directory can read the token: avoid it, or at least use a token limited to one repository with a short expiration.
  • When a token expires or is revoked, the next operation fails with an authentication error; the helper then forgets it, and Git asks again.

Not in the URL

git clone https://sam-rivera:[email protected]/sam-rivera/homelab.git works, but the token is then saved in plain text in .git/config, and appears in the shell history. Let Git ask for it, or use a credential helper.

When the token lacks a permission

A token with Contents: Read-only can clone and pull, but a push is refused with an HTTP 403 error:

remote: Permission to sam-rivera/homelab.git denied to sam-rivera.
fatal: unable to access 'https://github.com/sam-rivera/homelab.git/': The requested URL returned error: 403

The same error appears when the token does not include the repository at all. Edit the token's permissions on its page, or generate a new token.

Managing tokens

The token pages (Settings > Developer settings > Personal access tokens) list every token with its name, expiration, and last use. From there you can:

  • regenerate a token, to extend it with a new value;
  • edit a fine-grained token's repositories and permissions;
  • delete a token: it stops working immediately.

GitHub sends an email a few days before a token expires, and deletes classic tokens that have not been used for a year.

Summary

Action How
Create a fine-grained token Avatar > Settings > Developer settings > Personal access tokens > Fine-grained tokens > Generate new token
Use it Paste it at Git's Password prompt
Remember it for a while git config --global credential.helper 'cache --timeout=3600'
Forget it now git credential-cache exit
Revoke it Delete it on the token page

Common mistakes

  • Typing the account password at Git's prompt. Refused since August 13, 2021: use a token.
  • Waiting for the token to appear while pasting. Nothing is echoed; paste once and press Enter.
  • Closing the page before copying the token. It is shown only once: generate a new one.
  • Choosing too few permissions. Without Contents: Read and write, the push fails with a 403 error.
  • Choosing too many permissions, or no expiration. A leaked token is then a disaster.

Hands-on labs

Three labs, from guided to more autonomous. They need your GitHub account and Git. Replace <username> with your GitHub username. Each lab deletes the tokens it creates.

If Git does not ask for credentials

If a credential manager is already configured on your machine (git config --get-all credential.helper prints something), Git may use saved credentials and never show the prompts described below. To follow the labs as written, run the Git commands with -c credential.helper= (for example git -c credential.helper= clone ...), which disables saved credentials for that command only.

Lab 1: clone and push with a fine-grained token

Objective: create a token limited to one private repository, and use it to clone and push.

Prerequisites and initial state: a GitHub account, Git installed.

Setup: on GitHub, create a private repository homelab-practice-7 with a README. Then:

mkdir -p ~/git-practice/pat && cd ~/git-practice/pat

Tasks:

  1. Generate a fine-grained token named homelab-practice-7, with a 7-day expiration, access to only homelab-practice-7, and Contents: Read and write. Copy it into a temporary place you will clear at the end (for example a password manager entry).
  2. Clone the repository into ~/git-practice/pat, using your username and the token.
  3. In the clone, configure a name and email, add pi-dns,192.168.1.10,dns to a new file inventory.csv, commit with the message Create inventory, and push.
  4. On GitHub, check that the commit arrived. On the token page, check the token's Last used information.

Expected result and verification:

  • Typing the account password instead of the token fails with Support for password authentication was removed on August 13, 2021.
  • With the token, the clone succeeds, and the push prints main -> main.
  • The repository page shows inventory.csv and the commit Create inventory.
Solution
cd ~/git-practice/pat
git clone https://github.com/<username>/homelab-practice-7.git
# Username: <username>   Password: <paste the token>
cd homelab-practice-7
git config user.name "Sam Rivera"
git config user.email "[email protected]"
echo "pi-dns,192.168.1.10,dns" > inventory.csv
git add inventory.csv
git commit -m "Create inventory"
git push origin main
# Username and token again: no credential helper yet
  • The token is asked again at the push: without a credential helper, Git does not remember anything.
  • The commit's author is the name and email configured in the clone; the token only proves you are allowed to push.

Keep the clone, the repository, and the token for Lab 2.

Lab 2: the cache, and the read-only token

Objective: stop typing the token at each operation, then observe what happens with a token that lacks the write permission.

Prerequisites and initial state: the clone, repository, and token from Lab 1.

Setup:

cd ~/git-practice/pat/homelab-practice-7
git config credential.helper 'cache --timeout=300'

The setup configures the cache in this repository only (no --global), for 5 minutes.

Tasks:

  1. Add nas,192.168.1.20,storage to inventory.csv, commit, and push. Enter the token when asked.
  2. Make another change, commit, and push again. Note whether Git asks for the token.
  3. Run git credential-cache exit, then pull. Note whether Git asks for the token.
  4. On GitHub, generate a second token homelab-practice-7-ro, with access to the same repository and Contents: Read-only.
  5. Run git credential-cache exit. Make a change, commit, and push with the read-only token. Read the error. Then pull with the same token.

Expected result and verification:

  • Task 2: no question; the cached token is reused.
  • Task 3: Git asks for the username and token again.
  • Task 5: the push fails with remote: Permission to <username>/homelab-practice-7.git denied to <username>. and The requested URL returned error: 403; the pull succeeds.
Solution
cd ~/git-practice/pat/homelab-practice-7

# 1-2. The second push reuses the cached token
echo "nas,192.168.1.20,storage" >> inventory.csv
git commit -am "Add NAS"
git push                           # token asked, then cached
echo "web01,192.168.1.30,web" >> inventory.csv
git commit -am "Add web server"
git push                           # no question

# 3. Forget it
git credential-cache exit
git pull                           # token asked again

# 5. Read-only token
git credential-cache exit
echo "printer01,192.168.1.40,printer" >> inventory.csv
git commit -am "Add printer"
git push                           # 403 with the read-only token
git pull                           # reading is allowed
  • git pull on a private repository needs credentials too: reading a private repository is an authenticated operation.
  • After the 403, the commit Add printer is still in your local repository; it will be pushed with the read-write token.
  • Push it now with the read-write token if you want (git credential-cache exit, then git push).

Clean up when you are done: git credential-cache exit, rm -rf ~/git-practice/pat; on GitHub, delete both tokens and the repository homelab-practice-7.

Lab 3: the leaked token

Objective: handle a token leak from start to finish, as Sam when Alex reports that a token was pasted into a public issue.

Prerequisites and initial state: a GitHub account, Git installed. No setup: you prepare the situation yourself.

Tasks:

  1. Create a private repository homelab-practice-8 with a README, and a fine-grained token leaked-token with read and write access to its contents. Clone the repository in ~/git-practice/leak with the token, using the store helper for that clone only. (This is the bad practice that caused the leak.)
  2. Find where the token is written on your disk, and display it.
  3. The token has leaked: revoke it as quickly as possible.
  4. Prove that the leaked token is now useless, then remove it from the disk.
  5. Restore a working setup with a new token, without storing it in plain text.

Expected result and verification:

  • Task 2: ~/.git-credentials contains a line https://<username>:[email protected].
  • Task 4: a git pull with the stored token fails with an authentication error; the line is gone from ~/.git-credentials afterwards.
  • Task 5: a new token works, kept in memory by the cache helper; ~/.git-credentials does not contain it.
Solution
# 1. The bad practice
mkdir -p ~/git-practice/leak && cd ~/git-practice/leak
git -c credential.helper=store clone https://github.com/<username>/homelab-practice-8.git
cd homelab-practice-8
git config credential.helper store

# 2. In plain text, in your home directory
cat ~/.git-credentials

# 3. On GitHub: Settings > Developer settings > Personal access tokens >
#    Fine-grained tokens > leaked-token > Delete

# 4. The stored token no longer works
git pull                           # authentication fails
grep github.com ~/.git-credentials # the helper erased the rejected line (or remove it by hand)

# 5. A new token, kept in memory only
git config credential.helper 'cache --timeout=3600'
git pull                           # enter the new token
  • Revoke first, investigate after: deleting the token stops the damage immediately. Then check what it could access, and the repository's recent activity.
  • If ~/.git-credentials existed before the lab with other entries, remove only the lines you added.
  • The real fix is to never store tokens in plain text, and to limit each token to what it needs.

Clean up when you are done: git credential-cache exit, rm -rf ~/git-practice/leak; on GitHub, delete the new token and the repository homelab-practice-8.