Account Security, Two-Factor Authentication, SSH Keys, Tokens, and Credential Hygiene: Guided Hands-On Workflow and Core Operations
Build a disposable authentication workflow: inspect account protection, create and remove a lab SSH key, authenticate GitHub CLI through the browser flow, and verify each identity without exposing secrets.
Learning objectives
- Inspect account security configuration without revealing second-factor secrets or recovery material.
- Generate a disposable Ed25519 SSH key with a dedicated filename, inspect its public fingerprint, register only the public key, test the resolved GitHub identity, and remove the key afterward.
-
Use the supported GitHub CLI browser/device flow and inspect
gh auth statuswithout displaying the stored token. - Perform a read-only authenticated API identity query and distinguish the caller identity from repository authorization.
- Explain when a temporary PAT is justified and how to create, store, test, and revoke it without copying it into source/history.
- Verify every change with before/after evidence and leave no lab credential behind.
1. Scenario and safety rules
You are onboarding a developer to an imaginary service called Atlas. The task is not to “make authentication work by any means.” It is to prove exactly which credential is used, confirm the resolved GitHub identity, avoid secret exposure, and remove disposable credentials at the end.
cat,
type, Get-Content,
gh auth token, or
gh auth status --show-token against a private
key/token. The only key file you display is the
.pub public key.
Use a dedicated key filename so there is no risk of overwriting an
existing id_ed25519. If your account is
employer-managed or governed by enterprise policy, do not add a test
key without authorization; use the read-only path and the synthetic
examples instead.
2. Preflight: identify tools, host, and current state
git --version
ssh -V
gh --version
gh auth status --active --hostname github.com
git config --show-origin --get-all credential.helper
ssh-add -l
If gh auth status reports no authenticated account,
that is not a reason to create a PAT. The supported default
gh auth login flow opens or directs you to a browser
and stores the resulting token in the system credential store when
one is available. We will use that flow in section 9.
Record only tool versions, active hostname/account (if any), credential-helper name, and loaded SSH fingerprints. Do not include secret values in your lab notes.
3. Inspect account protection without revealing recovery material
- In GitHub, open Settings → Password and authentication. Confirm whether 2FA is enabled and which method categories are configured.
- Confirm that recovery codes have been generated/stored, but do not open, regenerate, screenshot, or paste them for this lab.
- If passkeys or security keys are already configured, record only their descriptive names and that a backup factor exists.
- Open your account security log if available and identify recent legitimate authentication events by time/type without copying private account data into a public report.
4. Generate a disposable SSH authentication key
Create the key under ~/.ssh with a lab-specific
filename. Let ssh-keygen prompt for a passphrase; use a
real passphrase for a credential you would keep. A disposable
exercise is still the right place to learn the secure default.
Git Bash, Bash, or zsh
mkdir -p "$HOME/.ssh"
ssh-keygen -t ed25519 -C "github-auth-lab" -f "$HOME/.ssh/github_auth_lab_ed25519"
ssh-keygen -lf "$HOME/.ssh/github_auth_lab_ed25519.pub"
PowerShell
New-Item -ItemType Directory -Force "$HOME/.ssh" | Out-Null
ssh-keygen -t ed25519 -C "github-auth-lab" -f "$HOME/.ssh/github_auth_lab_ed25519"
ssh-keygen -lf "$HOME/.ssh/github_auth_lab_ed25519.pub"
The
ssh-keygen -lf
"$HOME/.ssh/github_auth_lab_ed25519.pub"
output is safe to record as a public-key fingerprint. It should show
the key size, a SHA256: fingerprint, the comment, and
algorithm. The private-key file has no .pub suffix;
never print it.
5. Register only the public key with GitHub
Before mutation, open
Settings → SSH and GPG keys and note the existing
authentication-key titles/fingerprints. Then select
New SSH key, choose an
authentication key, use a title such as
github-auth-lab-temp, and paste only the contents of
the .pub file.
Display the public half only
cat "$HOME/.ssh/github_auth_lab_ed25519.pub"
On PowerShell,
Get-Content "$HOME/.ssh/github_auth_lab_ed25519.pub" is
equivalent. The line starts with the public key type (for example
ssh-ed25519) and is intentionally shareable. Confirm
the newly listed GitHub fingerprint matches your local
ssh-keygen -lf result.
6. Test the exact key and understand GitHub’s unusual exit code
Git Bash, Bash, or zsh
ssh -i "$HOME/.ssh/github_auth_lab_ed25519" -o IdentitiesOnly=yes -T git@github.com
printf 'ssh exit=%s
' "$?"
PowerShell
ssh -i "$HOME/.ssh/github_auth_lab_ed25519" -o IdentitiesOnly=yes -T git@github.com
"ssh exit=$LASTEXITCODE"
On first contact, OpenSSH may ask you to confirm the server host key. Do not accept an arbitrary fingerprint. Compare it with GitHub’s published SSH host-key fingerprints first. After successful authentication, GitHub prints a greeting containing the resolved username and explains that shell access is not provided.
1 even after successful authentication.
Therefore the username/success message proves identity; a generic
“nonzero means authentication failed” script is wrong for this
specific test.
7. Prove the key can read a repository without pushing anything
A functional Git transport test is stronger than a banner check.
Choose a repository you are allowed to read and use
GIT_SSH_COMMAND to force this exact key for one command
without rewriting your permanent SSH config:
GIT_SSH_COMMAND='ssh -i "$HOME/.ssh/github_auth_lab_ed25519" -o IdentitiesOnly=yes' git ls-remote git@github.com:OWNER/REPO.git HEAD
The output should contain an object ID followed by
HEAD. No branch is modified;
git ls-remote asks the remote repository which refs it
advertises. Replace OWNER/REPO with a repository you
legitimately control or can read. PowerShell learners can set
$env:GIT_SSH_COMMAND for the single terminal session,
run git ls-remote, then remove the environment
variable.
8. Remove the disposable SSH key and prove revocation
Return to Settings → SSH and GPG keys, locate
github-auth-lab-temp by title/fingerprint, and delete
it. Then repeat the exact-key SSH test. It should no longer
authenticate with that key. Only after hosted revocation is verified
should you remove the local files.
Git Bash, Bash, or zsh
rm -f "$HOME/.ssh/github_auth_lab_ed25519" "$HOME/.ssh/github_auth_lab_ed25519.pub"
PowerShell
Remove-Item -Force "$HOME/.ssh/github_auth_lab_ed25519","$HOME/.ssh/github_auth_lab_ed25519.pub" -ErrorAction SilentlyContinue
If the private key had been loaded into an SSH agent, remove that identity from the agent as well. Do not use broad commands that unload every key unless that is genuinely your intent.
9. Authenticate GitHub CLI with the supported browser flow
If gh is not authenticated, use the web flow rather
than manually creating a PAT:
gh auth login --hostname github.com --web
gh auth status --active --hostname github.com
gh auth status --json hosts
Current GitHub CLI documentation describes browser-based login as
the default authentication mode. When a system credential store is
available, gh stores the resulting token there. If no
suitable credential store is found, gh can fall back to
a plain-text file and reports the stored location via auth status;
that fallback is a reason to review local file
permissions/credential-store setup, not to print the token.
--show-token and do not run
gh auth token for this exercise. The goal is to verify
authentication state, not extract the bearer credential.
10. Use the managed gh credential for a read-only API identity check
gh api /user --jq '{login: .login, id: .id}'
gh api /rate_limit --jq '{core: .resources.core, graphql: .resources.graphql}'
The first request proves which GitHub user the CLI credential represents. The second shows rate-limit state without mutating anything. Do not publish the numeric account ID as sensitive information in a public report unless you have a reason; for the lab, it is enough to compare it locally with the expected account.
When you build raw REST clients later, send
Accept: application/vnd.github+json and an explicit
X-GitHub-Api-Version: 2026-03-10 header. This chapter
uses gh api for the mandatory path because it avoids
teaching learners to paste a token into a command line.
11. Optional PAT exercise: create one only for a concrete need
You do not need a PAT merely because APIs exist. If you want to practice token lifecycle, create a fine-grained PAT in Settings → Developer settings → Personal access tokens → Fine-grained tokens with a descriptive name, short expiration, your personal account as resource owner, and only one disposable repository selected.
Choose only the read permission needed by the planned endpoint. Do not paste the token into a command, remote URL, source file, screenshot, terminal recording, or chat. Store it temporarily in a secure local mechanism, make one read-only request, and delete the token from GitHub immediately afterward.
GITHUB_TOKEN or GitHub Apps for many automation cases.
Creating a PAT without a concrete requirement teaches the wrong
habit.
12. Challenge: choose the correct authentication surface
| Need | Best first choice | Why |
|---|---|---|
| Interactive website sign-in with phishing-resistant UX | Passkey or strong 2FA configuration | Protects the human account; not a Git transport credential. |
| Routine Git over SSH from one laptop | Device SSH key with passphrase / hardware-backed key | Private key stays local; public key is registered to the account. |
| Routine Git over HTTPS | GCM or GitHub CLI credential integration | Avoids manually managing a PAT for ordinary Git use. |
Interactive gh use |
gh auth login --web |
Browser/device flow plus secure local credential storage. |
| Workflow needs only its repository | Built-in GITHUB_TOKEN |
Job-scoped, repository-bounded GitHub App installation token. |
| Long-running organization integration | GitHub App installation token | Distinct app identity, install scope, short-lived installation tokens. |
13. Small failure drills
Drill A — SSH greeting names the wrong account.
Stop. Do not add more keys at random. Run the exact-key test with
-i and IdentitiesOnly=yes, inspect
~/.ssh/config host aliases, and verify which public key
is registered to which account.
Drill B — gh auth status reports an invalid stored
credential.
Preserve the host/account name, then reauthenticate with the browser
flow or log out the stale account intentionally. Do not use
--show-token to “debug” by exposing the value.
Drill C — HTTPS Git asks for a password. GitHub account passwords are not the modern Git-over-HTTPS credential. Configure GitHub CLI/GCM or use a PAT only when appropriate; do not place the PAT in the remote URL.
14. Verification checklist
- The lab SSH key used a dedicated filename and never overwrote an existing key.
-
Only the
.pubfile was displayed or uploaded; the private key was never printed. - The local public-key fingerprint matched the fingerprint shown by GitHub.
- The SSH greeting resolved to the intended GitHub username and you understood the documented exit code 1.
- The hosted SSH key was removed and removal was verified before local deletion.
-
gh auth statuswas used without--show-token, and API identity was verified throughgh api /user. - No PAT was created unless you had a specific read-only exercise and an explicit revocation step.
Knowledge check
Why use a custom SSH key filename in a lab?
It prevents overwriting or confusing an existing production/personal key and makes registration, fingerprint matching, and cleanup unambiguous.
What does ssh -i KEY -o IdentitiesOnly=yes prove
more clearly than a plain ssh -T?
It constrains the test to the specified key instead of allowing the SSH agent/client to offer other identities that could mask which credential actually succeeded.
Why is gh auth login --web preferable to manually
creating a PAT for routine interactive CLI use?
The supported browser flow handles authorization and lets gh use the system credential store when available, reducing manual bearer-token handling.
A successful SSH test exits 1. What should a functional read-only Git test use instead?
Use an operation such as git ls-remote against a
repository you can read; it tests Git transport/function without
modifying refs.
What must happen before deleting a lab private key from disk?
Remove/revoke its public-key registration on GitHub and verify that the key no longer authenticates, so you have evidence the server-side credential was actually retired.
15. Summary
A secure onboarding workflow is observable: inspect first, create one credential for one purpose, verify the resolved identity, perform the smallest functional test, and remove disposable access with proof. No step requires printing secret material.
You now have operational experience with the three most common developer paths: account protection in the web UI, Git transport over SSH, and GitHub CLI/API access through a managed browser authentication flow. Lesson 03 turns these operations into durable selection policy.
Authoritative references
Configuring two-factor authentication
Configuring 2FA recovery methods
Generating a new SSH key and adding it to the ssh-agent
Adding a new SSH key to your GitHub account
Testing your SSH connection
gh auth login
gh auth status
Caching GitHub credentials in Git
Managing personal access tokens
Authenticating to the REST API
REST API versions
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.