Expand description
The OAuth 2.0 Device Authorization Grant against the published GitHub App.
This is the only authentication path the product has (D3, D16). It needs
a public client_id and nothing else: no client secret, no redirect
listener, no loopback port, and no server anywhere in the design. GitHub
documents that a public client cannot secure a client secret, and this design
never tries to (07-security.md, “Authentication model”).
§The shape of a login
DeviceFlow::startposts theclient_idand gets back a user code, a device code, an expiry, and a polling interval.- The caller displays the user code and the canonical
crate::DEVICE_VERIFICATION_PATHURL. It displays nothing else — see “Phishing” below. DeviceFlow::completepolls until the user approves, honouring the interval and every documented error in the matrix.- The token is returned. Nothing here writes it anywhere;
d2owns the machine-scoped store andf1owns the wiring.
§The error matrix is four outcomes, not one failure
authorization_pending, slow_down, expired_token and access_denied are
four different things that each need a different response, and collapsing
them into one generic error is how a CLI ends up telling a user who declined
the authorization to try again:
GitHub error | Here | Caller does |
|---|---|---|
authorization_pending | PollOutcome::Pending | keep polling, same interval |
slow_down | PollOutcome::SlowDown | keep polling, longer interval |
expired_token | DeviceFlowError::Expired | start a whole new login |
access_denied | DeviceFlowError::AccessDenied | stop; the user said no |
Only the first two are recoverable, and DeviceFlowError::is_retryable
says so for the rest.
§Phishing
07-security.md’s threat table names “a phishing page imitates the
device-flow prompt to harvest a code”, with the control “the tool prints the
canonical github.com/login/device URL and never proxies or embeds the
approval page”. Two things implement it. The tool prints
crate::Endpoints::verification_url, a compiled-in constant, rather than
whatever a response contained; and DeviceFlow::start rejects a
verification_uri whose origin is not the configured GitHub web host, so a
response that tries to redirect a user elsewhere is an error rather than
something the CLI renders.
§Renewal
There is none, and none may be added. The published App opts out of
user-token expiration, so GitHub issues no renewal token with the access
token; renewing a user token requires the client secret, which a public
client cannot hold. lib.rs’s
tests::no_renewal_path_and_no_confidential_credential_in_this_crate scans
this file and lib.rs for the identifiers such a path would need — after
lower-casing and removing _, so that every casing a Rust identifier can
take is the same needle. That normalisation is why the prose here writes
“renewal token” and “client secret” as separate words.
- is deliberately not removed from a .rs file, because a Rust
identifier cannot contain one and removing it made ordinary hyphenated
English trip the gate. The manifest is normalised the other way, where - is
a kebab-case word separator rather than a hyphen. lib.rs’s
tests::normalise_source carries the reasoning and the residual gap.
Structs§
- Device
Authorization - What
DeviceFlow::startreturns: everything the login needs, with the one secret in it wrapped. - Device
Flow - The device-flow client.
Enums§
- Device
Flow Error - Every way a login can fail.
- Poll
Outcome - The result of one poll.
Constants§
- DEFAULT_
POLL_ INTERVAL - The interval used when a response omits one. RFC 8628’s default.
- DEVICE_
GRANT_ TYPE - The grant type the device flow’s token request carries, verbatim from RFC 8628.
- MAX_
TRANSPORT_ RETRIES - How many consecutive retryable failures one
DeviceFlow::completeloop absorbs before giving up. - SLOW_
DOWN_ INCREMENT - The minimum a
slow_downlengthens the poll interval by.