On GitHub, how do classic PATs, fine-grained PATs, and App installation tokens differ?
answer
- whose identity does the token carry
- scope breadth versus repository list
- how long does it stay valid
- what happens when the employee leaves
- JWT exchanged for a one-hour token
basics
~20 sClassic PATs carry coarse scopes across every repository the user can reach. Fine-grained PATs pin one resource owner, selected repositories, per-permission access and an expiry. GitHub App installation tokens belong to the app, expire hourly, and are the right choice for automation.
solid answer
~60 sA **classic personal access token** authenticates as the user and carries broad scopes such as `repo` or `workflow`; `repo` grants read and write to every repository that user can access, public and private, so blast radius follows the human, not the job. A **fine-grained personal access token** still acts as the user, but is bound to one resource owner, an explicit list of repositories, and per-permission read or write settings (Contents, Pull requests, Issues, …), with an expiry that is mandatory and an org-approval gate the organisation can require. A **GitHub App installation token** is a different identity altogether: you sign a short-lived JWT with the app's private key, exchange it at `POST /app/installations/{installation_id}/access_tokens`, and get a token valid for one hour scoped to that installation's permissions and repositories. For automation, the app token is the right default — it is not tied to an employee who may leave, it expires on its own, its actions are attributed to the app in the audit log, and its rate limit is the installation's rather than a person's.
code
bash · 16 lines# $JWT is RS256-signed with the app private key, exp <= 10 minutes
curl -s -X POST \
-H "Authorization: Bearer $JWT" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/app/installations/$INSTALLATION_ID/access_tokens \
-d '{"repositories":["widgets"],"permissions":{"contents":"read","pull_requests":"write"}}'
# {
# "token": "ghs_xxxxxxxxxxxxxxxxxxxx",
# "expires_at": "2026-08-19T13:00:00Z",
# "permissions": { "contents": "read", "pull_requests": "write" },
# "repository_selection": "selected"
# }
curl -s -H "Authorization: Bearer ghs_xxxxxxxxxxxxxxxxxxxx" \
https://api.github.com/repos/acme/widgets/pullsgo deeper
Recognise that GitHub has more than one kind of token and that a classic PAT with the repo scope is broad. Knowing that fine-grained tokens pick specific repositories is enough at this stage.
Explain the three models concretely: scopes versus per-permission grants, repository selection, mandatory expiry, and the JWT-to-installation-token exchange that produces a one-hour app credential.
Show the operational judgment: which credential each workload gets, why GITHUB_TOKEN beats a PAT in Actions, how you minimise blast radius by minting narrowed installation tokens, and what leak response looks like for each type.
Own the credential strategy for the organisation — approval policy for fine-grained tokens, where app private keys live and how they rotate, how automation identity is audited, and the migration path off personal tokens.
## Why there are three All three answer "who is this request?", but they differ in whose identity is used, how narrowly access can be cut, and how long the credential lives. Interviews probe this because picking the wrong one is the most common cause of over-privileged automation. ## Classic personal access token Created by a user, authenticates **as that user**, and is authorised by **scopes** — coarse capability labels such as `repo`, `workflow`, `read:org`, `gist`, `admin:repo_hook`. The critical property is that scopes are not per-repository. `repo` means "read and write everything this user can touch", so a token minted for one CI job can reach every private repository in every organisation the user belongs to. Expiry is optional. Tokens begin with the prefix `ghp_`. Use it only where nothing better exists, and treat every classic PAT in CI as a standing incident risk: if it leaks, the attacker inherits the user. ## Fine-grained personal access token Also acts as the user, but the authorisation model is different: - **One resource owner per token.** A token is issued against your personal account or a single organisation, and cannot span both. - **Explicit repository selection** — all current repositories of that owner, or a chosen list. - **Per-permission access levels.** Instead of `repo`, you grant Contents: read, Pull requests: write, Issues: read, and so on. Metadata: read is implicit because almost everything needs it. - **Mandatory expiration.** - **Organisation control.** An organisation decides whether fine-grained tokens may access it at all, and can require an owner to approve each token before it works. These tokens carry the `github_pat_` prefix. They are a genuine improvement over classic PATs for a human's scripting, and the org-side controls mean security teams can actually see and approve what exists. ## GitHub App installation token A GitHub App is a first-class actor. You register it once with a set of requested permissions and a private key; organisations and users then **install** it on selected repositories, granting the permissions at install time. Authentication is two-legged: 1. Build a JWT signed with the app's private key (RS256) whose `iss` identifies the app and whose `exp` is at most ten minutes out. That JWT authenticates *as the app* and can only call app-level endpoints such as listing installations. 2. Exchange it: `POST /app/installations/{installation_id}/access_tokens` returns an **installation access token** (prefix `ghs_`) valid for one hour. Optionally pass `repositories` and `permissions` in that request to mint a token narrower than the installation itself — a strong pattern for a job that only needs one repo. Properties that matter operationally: - **No human owner.** Nobody's departure or MFA reset breaks it. - **Short-lived by construction.** A leaked installation token is worthless within an hour; a leaked private key is the real crown jewel, so that is what you protect and rotate. - **Attribution.** Actions appear as the app in the audit log and on commits, which makes forensic review far easier than "the service account did it". - **Rate limit is per installation** rather than shared with everything that user does, and scales with the installation's size. - **Acting as a user is still possible** through the user-to-server flow, where the effective permissions are the intersection of the app's permissions and what that user can do. ## The Actions case Inside a GitHub Actions workflow you already have `GITHUB_TOKEN`, an installation token minted for that run and revoked when the job finishes. Its permissions are configurable per workflow or job, and it is scoped to the running repository. Reaching for a PAT in Actions is usually a smell — the two legitimate reasons are needing access to a *different* repository, and needing an event triggered by the token to start another workflow, which `GITHUB_TOKEN` deliberately does not do. The right fix for cross-repository access is a GitHub App, not a personal token. ## Choosing - A human running scripts on their laptop: fine-grained PAT, narrow repositories, short expiry. - CI inside one repository: `GITHUB_TOKEN` with least-privilege `permissions`. - A bot, service, or anything acting across repositories or organisations: GitHub App installation token. - Classic PAT: only when a required API or feature has no fine-grained or app equivalent, and then with the shortest expiry you can tolerate. ## What interviewers listen for The phrase "tied to a person" — recognising that any personal token, classic or fine-grained, dies with the account and inherits the human's reach, while an app token is organisational infrastructure. Second, that short lifetimes shift the security question from "how do we stop leaks" to "how fast does a leak stop mattering".
- What exactly do you protect for a GitHub App, given installation tokens expire in an hour?The app's private key and its webhook secret. The key mints JWTs, which mint installation tokens, so it is the root of the whole chain and never expires on its own. Store it in a secrets manager, never in the repository, restrict which systems can read it, and support rotation — GitHub allows multiple registered keys so you can roll one in before removing the old.
- Why is a personal access token usually the wrong credential inside GitHub Actions?Because every run already gets GITHUB_TOKEN, an installation token scoped to the repository, revoked at job end, with per-job permissions you set explicitly. A PAT is broader, longer-lived, tied to a person, and invisible in the workflow's permission model. The genuine exceptions are cross-repository access and needing the pushed event to trigger another workflow — and the first is better solved with a GitHub App.
- An organisation wants to stop developers from creating tokens that reach every repository. What is available?Organisations can control fine-grained personal access tokens: whether they may access the organisation at all, and whether an owner must approve each one before it works, with a list of approved tokens to review. Classic PATs can be restricted from accessing the organisation as well. Beyond that, moving automation onto GitHub Apps removes personal tokens from the picture entirely.
- Can an installation token be narrower than the installation itself?Yes. When exchanging the JWT you may pass repositories and permissions in the request body, and the resulting token is the intersection of what you asked for and what the installation actually has. A job that only reads one repository should mint exactly that, so a leaked token during that hour is nearly harmless.
saying these in an interview costs you the question
- Says fine-grained PATs are just classic PATs renamed
- Treats an App installation token as belonging to a user
- Puts a long-lived classic PAT in CI for convenience
- Thinks the app private key expires like the token does
- Believes GITHUB_TOKEN can reach any repository in the org