A library for Go client applications that need to perform OAuth authorization against a server, typically GitHub.com.
Traditionally, OAuth for web applications involves redirecting to a URI after the user authorizes an app. While web apps (and some native client apps) can receive a browser redirect, client apps such as CLI applications do not have such an option.
To accommodate client apps, this library implements the OAuth Device Authorization Grant which GitHub.com now supports. With Device flow, the user is presented with a one-time code that they will have to enter in a web browser while authorizing the app on the server. Device flow is suitable for cases where the web browser may be running on a separate device than the client app itself; for example a CLI application could run within a headless, containerized instance, but the user may complete authorization using a browser on their phone.
To transparently enable OAuth authorization on any GitHub host (e.g. GHES instances without OAuth “Device flow” support), this library also bundles an implementation of OAuth web application flow in which the client app starts a local server at http://127.0.0.1:<port>/ that acts as a receiver for the browser redirect. First, Device flow is attempted, and the localhost server is used as fallback. With the localhost server, the user's web browser must be running on the same machine as the client application itself.
Applications that need more control over the user experience around authentication should directly interface with github.com/cli/oauth/device and github.com/cli/oauth/webapp packages.
In theory, these packages would enable authorization on any OAuth-enabled host. In practice, however, this was only tested for authorizing with GitHub.
Set RequestRefreshToken to request an expiring access token and refresh token:
flow := &oauth.Flow{
Host: host,
ClientID: clientID,
Scopes: []string{"repo"},
RequestRefreshToken: true,
}
requestedAt := time.Now().UTC()
token, err := flow.DetectFlow()
if err != nil {
return err
}Servers that do not support expiring tokens may ignore the request and return a token without expiration metadata or a refresh token.
The expiry fields are relative durations. Calculate and persist an absolute expiration time when a token is issued:
expiresAt := time.Time{}
if token.ExpiresIn > 0 {
expiresAt = requestedAt.Add(token.ExpiresIn)
}
if err := saveToken(token, expiresAt); err != nil {
return err
}After loading the token and its persisted expiration time, exchange an expired token's refresh token and persist the returned token pair:
if token.RefreshToken != "" && !expiresAt.IsZero() && !time.Now().Before(expiresAt) {
requestedAt := time.Now().UTC()
refreshedToken, err := oauth.Refresh(oauth.RefreshOptions{
Host: host,
ClientID: clientID,
ClientSecret: clientSecret,
RefreshToken: token.RefreshToken,
HTTPClient: http.DefaultClient,
})
if err != nil {
return err
}
refreshedExpiresAt := time.Time{}
if refreshedToken.ExpiresIn > 0 {
refreshedExpiresAt = requestedAt.Add(refreshedToken.ExpiresIn)
}
if err := saveToken(refreshedToken, refreshedExpiresAt); err != nil {
return err
}
token = refreshedToken
expiresAt = refreshedExpiresAt
}GitHub rotates refresh tokens on every refresh, so always persist the refresh token returned by Refresh. Rotation is optional in OAuth, and when a server omits a new refresh token, Refresh returns the one that was passed in.

