| |

GitHub 03 🌐 SSH Keys and Personal Access Tokens (PATs) Authentication

⏱️ Estimated read: 12 min | |

GitHub disabled password authentication for Git operations over HTTPS in August 2021. Passwords are reused, phished, and leaked in bulk; the replacements—SSH keys and personal access tokens—are revocable, scoped, and often time-limited . The choice between them is not about which is “better.” It is about which one matches the workflow: SSH keys for your own machines, PATs for scripts, CI, and networks that block SSH.

This chapter covers both authentication methods. It begins with the SSH key pair—the private half that never leaves your machine and the public half that you give to GitHub—and the ssh-keygen workflow that generates it. It then explains the Personal Access Token: the classic token with broad scopes and the fine-grained token with repository-specific permissions that GitHub recommends. The final sections cover the security layers that authentication does not replace—2FA—and the credential storage that keeps the token out of your shell history.


Key point: SSH keys authenticate silently after setup. PATs authenticate with a string that you paste in place of a password. Neither replaces two-factor authentication, which protects the account login, not the Git operations.


Why passwords are gone

The reuse problem. A password that works for GitHub is a password that users reuse elsewhere. When another site is breached, the reused password becomes a GitHub credential. GitHub cannot prevent reuse, so it removed the password from the Git operation entirely .

The revocation problem. A leaked password is difficult to revoke without changing the account’s login credential. An SSH key or a PAT can be revoked individually: delete the key, delete the token, and the rest of the account is unaffected .

The scoping problem. A password grants access to everything the account can reach. A fine-grained PAT grants access to specific repositories with specific permissions. A leaked fine-grained token exposes only what it was scoped to, not the entire account .


a. SSH keys: generation, agent, and GitHub

An SSH key pair has two halves. The private key stays on your machine and never leaves it. The public key is added to GitHub, and GitHub uses it to verify that you hold the private key. Anyone with the public key can verify you, but cannot impersonate you .

The generation command uses Ed25519, the modern algorithm. The -C flag adds a comment—typically the email address—that appears in the key file and helps identify the key later.

# Generate a new Ed25519 SSH key.
# Replace the email with the one associated with your GitHub account.
ssh-keygen -t ed25519 -C "your_email@example.com"

The command prompts for a file location (press Enter for the default ~/.ssh/id_ed25519) and a passphrase. The passphrase is optional but recommended: it encrypts the private key on disk, so a stolen key file is useless without the passphrase .

The SSH agent is the program that keeps the decrypted key in memory, so you do not retype the passphrase on every Git operation. Start the agent and add the key.

# Start the SSH agent in the background.
eval "$(ssh-agent -s)"

# Add the private key to the agent.
ssh-add ~/.ssh/id_ed25519

The public key must be copied to GitHub. The cat command prints it, and the clipboard command depends on the operating system.

# Print the public key to the terminal.
cat ~/.ssh/id_ed25519.pub

# On macOS, copy directly to clipboard.
pbcopy < ~/.ssh/id_ed25519.pub

# On Windows Git Bash, copy directly to clipboard.
clip < ~/.ssh/id_ed25519.pub

On GitHub, the key is added under Settings → SSH and GPG keys → New SSH key. The title should identify the machine—”Work Laptop” or “Home Desktop”—so that the key can be revoked individually if the machine is lost . The key field accepts the entire public key string, starting with ssh-ed25519.

The connection test confirms that the key is working.

# Test the SSH connection to GitHub.
ssh -T git@github.com

# Expected output:
# Hi username! You've successfully authenticated, but GitHub does not provide shell access.

The “does not provide shell access” message is normal. It means the authentication succeeded, but GitHub does not allow interactive shell sessions .


b. Personal Access Tokens: classic and fine-grained

A Personal Access Token is a string that takes the place of a password when using HTTPS remote URLs. GitHub offers two types, and the choice matters for security.

Classic tokens have broad scopes that apply to every repository the account can access. The repo scope grants full control of private repositories; read:org grants read access to organization membership. Classic tokens are required for a few legacy scenarios—user-owned Projects V2, external collaborator access, and some REST endpoints that fine-grained tokens do not yet support . They are also the higher-risk option: a leaked classic token exposes the entire account .

Fine-grained tokens are repository-specific and permission-specific. The token is scoped to selected repositories, and the permissions are chosen from a list of over 50 granular options with read/write/none settings. Fine-grained tokens have a mandatory expiration (up to 366 days, organization-configurable), and organization administrators can require approval before the token is used . GitHub recommends fine-grained tokens for most scenarios, and the principle of least privilege is the reason .

The creation workflow for a fine-grained token begins at Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token. The form asks for a name, an expiration, a resource owner (the user or organization), the repositories the token can access, and the permissions for each repository .

1. Settings → Developer settings
2. Personal access tokens → Fine-grained tokens
3. Generate new token
4. Name: "terminal-access"
5. Expiration: 90 days
6. Resource owner: your username
7. Repository access: Only select repositories
8. Permissions: Contents (Read and write), Pull requests (Read and write)
9. Generate token
10. Copy the token immediately

The token is displayed once. GitHub does not store it in a recoverable form, and the page will not show it again after it is closed . The token must be copied to a password manager or a credential helper before leaving the page.

The classic token workflow is similar but simpler and less secure. The scopes are broad, and the token applies to every repository the account can access .

1. Settings → Developer settings
2. Personal access tokens → Tokens (classic)
3. Generate new token (classic)
4. Name: "terminal-access"
5. Expiration: 90 days
6. Scopes: repo
7. Generate token
8. Copy the token immediately

When Git asks for a password over HTTPS, the token is pasted instead. The Git credential helper can cache the token so it does not need to be pasted every time .


c. Security layers and credential storage

The authentication method and the account login are separate layers. SSH keys and PATs authenticate Git operations—push, pull, clone. Two-factor authentication protects the account login on the GitHub website. GitHub requires 2FA for most accounts, and the requirement is independent of the Git authentication method .

The credential helper is the storage mechanism that prevents the token from being typed into the terminal repeatedly. The helper stores the token in the operating system’s credential store, and Git retrieves it automatically when needed.

# Store credentials using the system's credential helper.
git config --global credential.helper store

# Or use the platform-specific helper.
# macOS:
git config --global credential.helper osxkeychain
# Windows:
git config --global credential.helper manager

The store helper writes the token to a plain text file in the home directory. It is the simplest option and the least secure. The platform-specific helpers use the operating system’s keychain or credential manager, which encrypts the token at rest.

The token must never be committed to a repository. A token in a config file or an environment variable that is checked in is a token that GitHub’s secret scanning will detect and revoke . The token belongs in a credential helper, a CI secret, or a password manager.


Complete Example Session

# ============================================
# PART 1: CHECK FOR EXISTING SSH KEYS
# ============================================
# List the .ssh directory.

ls -al ~/.ssh

# If id_ed25519 and id_ed25519.pub exist, skip to Part 3.

# ============================================
# PART 2: GENERATE THE SSH KEY
# ============================================
# Ed25519 is the modern algorithm.

ssh-keygen -t ed25519 -C "your_email@example.com"

# Press Enter for default location.
# Set a passphrase or press Enter for none.

# ============================================
# PART 3: ADD THE KEY TO THE AGENT
# ============================================
# The agent keeps the key in memory.

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

# ============================================
# PART 4: COPY THE PUBLIC KEY
# ============================================
# The public key is what goes to GitHub.

cat ~/.ssh/id_ed25519.pub

# ============================================
# PART 5: ADD THE KEY TO GITHUB
# ============================================
# Settings → SSH and GPG keys → New SSH key.

# Title: "Work Laptop"
# Key: paste the output of the cat command.
# Add SSH key.

# ============================================
# PART 6: TEST THE CONNECTION
# ============================================

ssh -T git@github.com

# Expected: Hi username! You've successfully authenticated...

# ============================================
# PART 7: CREATE A FINE-GRAINED PAT
# ============================================
# Settings → Developer settings → Fine-grained tokens.

# Name: "terminal-access"
# Expiration: 90 days
# Repository access: Only select repositories
# Permissions: Contents (Read and write), Pull requests (Read and write)
# Generate token.
# Copy the token immediately.

# ============================================
# PART 8: STORE THE TOKEN
# ============================================
# Use the credential helper.

git config --global credential.helper store

# The next HTTPS push prompts for username and password.
# Paste the token as the password.

# ============================================
# PART 9: SWITCH A REMOTE FROM HTTPS TO SSH
# ============================================
# If the repository was cloned with HTTPS.

git remote set-url origin git@github.com:username/repository.git

# ============================================
# PART 10: THE FULL PICTURE
# ============================================
# SSH keys: silent authentication after setup.
# PATs: token in place of password over HTTPS.
# Fine-grained: scoped to repositories and permissions.
# 2FA: protects the account login, separate layer.

echo "Authentication setup complete."

The ten parts walk through the SSH key generation, the GitHub addition, the connection test, and the PAT creation. The pattern is consistent: generate or create the credential, add it to GitHub, and test that it works.


Quick Reference

SSH Key Commands

CommandPurpose
ssh-keygen -t ed25519 -C "email"Generate a key pair
eval "$(ssh-agent -s)"Start the SSH agent
ssh-add ~/.ssh/id_ed25519Add the key to the agent
cat ~/.ssh/id_ed25519.pubPrint the public key
ssh -T git@github.comTest the connection

PAT Types

TypeScopeExpirationRecommended
ClassicBroad, account-wideOptionalNo
Fine-grainedRepository-specificMandatoryYes

SSH vs PAT

AspectSSH KeyPAT
Remote URLgit@github.com:...https://github.com/...
AuthenticationSilent after setupPaste token
Best forOwn machinesScripts, CI, firewalls
RevocationDelete the keyDelete the token

Security Layers

LayerProtects
SSH key or PATGit operations
2FAAccount login
Credential helperToken storage

Best Practices

✅ Do This:

# Generate Ed25519 keys.
ssh-keygen -t ed25519 -C "email"                                      // ✅
# Use a passphrase on the private key.
# The passphrase protects the key if the file is stolen.             // ✅
# Create fine-grained PATs scoped to specific repositories.
# Settings → Developer settings → Fine-grained tokens.               // ✅
# Copy the token immediately after creation.
# GitHub does not show it again.                                     // ✅
# Use the credential helper to store the token.
git config --global credential.helper store                           // ✅

❌ Don’t Do This:

# Don't use a classic PAT for new workflows.
# Fine-grained tokens are more secure.                               // ❌
# Don't commit a token to a repository.
# Secret scanning will detect and revoke it.                         // ❌
# Don't skip the connection test.
ssh -T git@github.com  # confirms the key works                      // ❌
# Don't assume SSH replaces 2FA.
# They are separate layers.                                          // ❌

Common Pitfalls

PitfallWhy It HappensFix
Permission denied (publickey)Key not added to agent or GitHubssh-add and verify the key on GitHub
PAT rejectedToken expired or wrong scopeCreate a new token with correct permissions
Token not shown againGitHub displays it onceCopy immediately, store in password manager
HTTPS asks for passwordPAT not usedPaste the token as the password
SSH connection failsUsing HTTPS URLgit remote set-url to SSH URL

Real-World Examples

1. Generate SSH Key

ssh-keygen -t ed25519 -C "you@example.com"

2. Start Agent

eval "$(ssh-agent -s)"

3. Add Key to Agent

ssh-add ~/.ssh/id_ed25519

4. Copy Public Key

cat ~/.ssh/id_ed25519.pub

5. Test Connection

ssh -T git@github.com

6. Switch Remote to SSH

git remote set-url origin git@github.com:user/repo.git

7. Create Fine-Grained PAT

Settings → Developer settings → Fine-grained tokens → Generate new token

8. Store Credentials

git config --global credential.helper store

9. Classic PAT Scopes

repo, read:org

10. Fine-Grained Permissions

Contents: Read and write
Pull requests: Read and write

Visual

SSH Key Pair

┌─────────────────────────────────────────────────────────┐
│  YOUR MACHINE                                           │
│  ┌───────────────────────────────────────────────────┐  │
│  │  PRIVATE KEY: ~/.ssh/id_ed25519                   │  │
│  │  → never leaves the machine                       │  │
│  │  → protected by passphrase (optional)             │  │
│  └───────────────────────────────────────────────────┘  │
│         │                                               │
│         │  public key added to GitHub                   │
│         ▼                                               │
│  ┌───────────────────────────────────────────────────┐  │
│  │  GITHUB                                           │  │
│  │  PUBLIC KEY: ssh-ed25519 AAAA...                  │  │
│  │  → verifies that you hold the private key         │  │
│  └───────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

The Authentication Choice

┌─────────────────────────────────────────────────────────┐
│  SSH KEY                        PAT                     │
│  git@github.com:user/repo       https://github.com/...  │
│                                                         │
│  Silent after setup             Paste token             │
│  Own machines                   Scripts, CI, firewalls  │
│  No expiration                  Mandatory expiration    │
└─────────────────────────────────────────────────────────┘

Classic vs Fine-Grained

┌─────────────────────────────────────────────────────────┐
│  CLASSIC                                                │
│  Scopes: repo, admin:org, gist                          │
│  Applies to all repositories                            │
│  Expiration optional                                    │
│  Higher risk if leaked                                  │
│                                                         │
│  FINE-GRAINED                                           │
│  Repository: selected                                   │
│  Permissions: 50+ granular options                      │
│  Expiration mandatory                                   │
│  Lower risk if leaked                                   │
└─────────────────────────────────────────────────────────┘

The Security Layers

┌─────────────────────────────────────────────────────────┐
│  ACCOUNT LOGIN                                          │
│  → 2FA (authenticator, security key)                    │
│  → protects the GitHub website                          │
│                                                         │
│  GIT OPERATIONS                                         │
│  → SSH key or PAT                                       │
│  → protects push, pull, clone                           │
│                                                         │
│  These are separate layers.                             │
│  SSH does not replace 2FA.                              │
└─────────────────────────────────────────────────────────┘

Summary

ItemValue
Password authenticationDisabled since August 2021
SSH key algorithmEd25519 (-t ed25519)
Key generationssh-keygen -t ed25519 -C "email"
Agenteval "$(ssh-agent -s)"
Add to agentssh-add ~/.ssh/id_ed25519
Testssh -T git@github.com
Classic PATBroad scopes, optional expiration
Fine-grained PATRepository-specific, mandatory expiration
2FAProtects account login, separate layer

Key takeaways:

  • Passwords no longer work for Git operations. SSH keys and PATs are the only options, and both are revocable and scoped .
  • SSH keys are the silent option. Generate with ssh-keygen -t ed25519, add the public key to GitHub, and pushes work without a token .
  • Fine-grained PATs are the recommended token type. They are scoped to specific repositories and permissions, with a mandatory expiration .
  • Classic PATs are for legacy scenarios. User-owned Projects V2 and some REST endpoints still require them, but they grant broad access and should be avoided otherwise .
  • The token is shown once. Copy it immediately and store it in a credential helper or password manager .
  • SSH and 2FA are separate layers. SSH authenticates Git operations; 2FA protects the account login. Neither replaces the other .

Remember: Authentication is the gate between your machine and GitHub. The SSH key is the silent key that opens the gate after a one-time setup. The PAT is the token that you paste when the gate asks for a password. Both are better than the password they replaced because they can be revoked individually and scoped to the minimum access needed. The credential helper keeps the token out of the shell history, and the 2FA keeps the account itself from being stolen. Set up both, and the gate fades into the background.


Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!