Since its creation, gh
has enforced a mapping of one account per host. Functionally, this meant that when targeting a
single host (e.g. github.com) each auth login
would replace the token being used for API requests, and for git
operations when gh
was configured as a git credential manager. Removing this limitation has been a long requested
feature, with many community members offering workarounds for a variety of use cases.
A particular shoutout to @gabe565 and his long term community support for https://github.com/gabe565/gh-profile in this space.
With the release of v2.40.0
, gh
has begun supporting multiple accounts for some use cases on github.com and
in GitHub Enterprise. We recognise that there are a number of missing quality of life features, and we've opted
not to address the use case of automatic account switching based on some context (e.g. pwd
, git remote
).
However, we hope many of those using these custom solutions will now find it easier to obtain and update tokens (via the standard
OAuth flow rather than as a PAT), and to store them securely in the system keyring managed by gh
.
We are by no means excluding these things from ever being native to gh
but we wanted to ship this MVP and get more
feedback so that we can iterate on it with the community.
The support for multiple accounts in this release is focused around auth login
becoming additive in behaviour.
This allows for multiple accounts to be easily switched between using the new auth switch
command. Switching the "active"
user for a host will swap the token used by gh
for API requests, and for git operations when gh
was configured as a
git credential manager.
We have extended the auth logout
command to switch account where possible if the currently active user is the target
of the logout
. Finally we have extended auth token
, auth switch
, and auth logout
with a
--user
flag. This new flag in combination with --hostname
can be used to disambiguate accounts when running
non-interactively.
Here's an example usage. First, we can see that I have a single account wilmartin_microsoft
logged in, and
auth status
reports that this is the active account:
➜ gh auth status
github.com
✓ Logged in to github.com account wilmartin_microsoft (keyring)
- Active account: true
- Git operations protocol: https
- Token: gho_************************************
- Token scopes: 'gist', 'read:org', 'repo', 'workflow'
Running auth login
and proceeding through the browser based OAuth flow as williammartin
, we can see that
auth status
now reports two accounts under github.com
, and our new account is now marked as active.
➜ gh auth login
? What account do you want to log into? GitHub.com
? What is your preferred protocol for Git operations on this host? HTTPS
? How would you like to authenticate GitHub CLI? Login with a web browser
! First copy your one-time code: A1F4-3B3C
Press Enter to open github.com in your browser...
✓ Authentication complete.
- gh config set -h github.com git_protocol https
✓ Configured git protocol
✓ Logged in as williammartin
➜ gh auth status
github.com
✓ Logged in to github.com account williammartin (keyring)
- Active account: true
- Git operations protocol: https
- Token: gho_************************************
- Token scopes: 'gist', 'read:org', 'repo', 'workflow'
✓ Logged in to github.com account wilmartin_microsoft (keyring)
- Active account: false
- Git operations protocol: https
- Token: gho_************************************
- Token scopes: 'gist', 'read:org', 'repo', 'workflow'
Fetching our username from the API shows that our active token correctly corresponds to williammartin
:
➜ gh api /user | jq .login
"williammartin"
Now we can easily switch accounts using gh auth switch
, and hitting the API shows that the active token has been
changed:
➜ gh auth switch
✓ Switched active account for github.com to wilmartin_microsoft
➜ gh api /user | jq .login
"wilmartin_microsoft"
We can use gh auth token --user
to get a specific token for a user (which should be handy for automated switching
solutions):
➜ GH_TOKEN=$(gh auth token --user williammartin) gh api /user | jq .login
"williammartin"
Finally, running gh auth logout
presents a prompt when there are multiple choices for logout, and switches account
if there are any remaining logged into the host:
➜ gh auth logout
? What account do you want to log out of? wilmartin_microsoft (github.com)
✓ Logged out of github.com account wilmartin_microsoft
✓ Switched active account for github.com to williammartin
As mentioned above, we know that this only addresses some of the requests around supporting multiple accounts. While these are not out of scope forever, for this release some of the big things we have intentionally not included are:
- Automatic account switching based on some context (e.g.
pwd
,git remote
) - Automatic configuration of git config such as
user.name
anduser.email
when switching - User level configuration e.g.
williammartin
usesvim
butwilmartin_microsoft
usesemacs
As in any MVP there are going to be some sharp edges that need to be smoothed out over time. Here are a list of known sharp edges in this release.
The trickiest piece of this work was that the hosts.yml
file only supported a mapping of one-to-one in the host to
account relationship. Having persistent data on disk that required a schema change presented a compatibility challenge
both backwards for those who use go-gh
outside of gh
, and forward for gh
itself
where we try to ensure that it's possible to use older versions in case we accidentally make a breaking change for users.
As such, from this release, running any command will attempt to migrate this data into a new format, and will
additionally add a version
field into the config.yml
to aid in our future maintainability. While we have tried
to maintain forward compatibility (except in one edge case outlined below), and in the worst case you should be able
to remove these files and start from scratch, if you are concerned about the data in these files, we advise you to take
a backup.
There is one known case using --insecure-storage
that we don't maintain complete forward and backward compatibility.
This occurs if you auth login --insecure-storage
, upgrade to this release (which performs the data migration), run
auth login --insecure-storage
again on an older release, then at some time later use auth switch
to make this
account active. The symptom here would be usage of an older token (which may for example have different scopes).
This occurs because we will only perform the data migration once, moving the original insecure token to a place where
it would later be used by auth switch
.
Some of our users lean on tools to manage their application configuration in an immutable manner for example using
https://github.com/nix-community/home-manager. These users will hit an error when we attempt to persist the new
version
field to the config.yml
. They will need to ensure that the home-manager
configuration scripts are updated
to add version: 1
.
See nix-community/home-manager#4744 for more details.
Although this has always been possible, the multi account flow increases the likelihood of doing something surprising
with auth refresh
. This command allows for a token to be updated with additional or fewer scopes. For example,
in the following example we add the read:project
scope to the scopes for our currently active user williammartin
,
and proceed through the OAuth browser flow as williammartin
:
➜ gh auth refresh -s read:project
? What account do you want to refresh auth for? github.com
! First copy your one-time code: E79E-5FA2
Press Enter to open github.com in your browser...
✓ Authentication complete.
➜ gh auth status
github.com
✓ Logged in to github.com account williammartin (keyring)
- Active account: true
- Git operations protocol: https
- Token: gho_************************************
- Token scopes: 'gist', 'read:org', 'read:project', 'repo', 'workflow'
✓ Logged in to github.com account wilmartin_microsoft (keyring)
✓ Logged in to github.com account wilmartin_microsoft (keyring)
- Active account: false
- Git operations protocol: https
- Token: gho_************************************
- Token scopes: 'gist', 'read:org', 'repo', 'workflow'
However, what happens if I try to remove the workflow
scope from my active user williammartin
but proceed through
the OAuth browser flow as wilmartin_microsoft
?
➜ gh auth refresh -r workflow
! First copy your one-time code: EEA3-091C
Press Enter to open github.com in your browser...
error refreshing credentials for williammartin, received credentials for wilmartin_microsoft, did you use the correct account in the browser?
When adding or removing scopes for a user, the CLI gets the scopes for the current token and then requests a new token with the requested amended scopes. Unfortunately, when we go through the account switcher flow as a different user, we end up getting a token for the wrong user with surprising scopes. We don't believe that starting and ending a refresh
as different accounts is
a use case we wish to support and has the potential for misuse. As such, we have begun erroring in this case.
Note that a token has still been minted on the platform but gh
will refuse to store it. We are investigating
alternative approaches with the platform team to put some better guardrails in place earlier in the flow.
When using auth login
with github.com, if a user has multiple accounts in the browser, they should be presented
with an interstitial page that allows for proceeding as any of their accounts. However, for Device Control Flow OAuth
flows, this feature has not yet made it into GHES.
For the moment, if you have multiple accounts on GHES that you wish to log in as, you will need to ensure that you
are authenticated as the correct user in the browser before running auth login
.