はじめに

先日GitHubのトークン期限が切れたたため、macOSで git push 時に以下のエラーが出ました。

remote: Invalid username or token.
Password authentication is not supported for Git operations.
fatal: Authentication failed for 'https://github.com/...'

PAT(Personal Access Token)を再発行し、キーチェーンアクセスから古い認証情報を削除しても解決しませんで、「ん?」となりました。

結論、credential helperの優先順位を見落としている可能性が高いです。

この記事では、macOS環境でGitが認証情報をどこから取得しているかを整理し、トークン期限切れ時に迷わず対処できるようにします。

Git の認証の仕組み:credential helper

Git自体はトークンを管理しません。認証が必要になると、credential helperという外部プログラムにトークンの取得を委任します。

現在の環境でどのcredential helperが設定されているかは、以下で確認することができます。

$ git config --show-origin --list | grep credential

macOSでgh CLIをインストールしている環境の、典型的な出力は以下です。

file:/Library/Developer/CommandLineTools/usr/share/git-core/gitconfig  credential.helper=osxkeychain
file:/Users/username/.gitconfig  credential.helper=osxkeychain
file:/Users/username/.gitconfig  credential.https://github.com.helper=
file:/Users/username/.gitconfig  credential.https://github.com.helper=!/opt/homebrew/bin/gh auth git-credential

注目すべき点が2つあります。

1. credential helper が複数登録されている

osxkeychain(キーチェーン)とgh auth git-credential(gh CLI)の両方が存在します。ホスト固有の設定(credential.https://github.com.helper)は汎用設定(credential.helper)より優先されるため、github.comへの認証ではgh CLIが使われます。

2. 空の helper でリセットされている

credential.https://github.com.helper=(値が空)の行は、「github.comに対して、これより前に定義された汎用helperをリセットする」という意味です。これによりosxkeychainが無効化され、直後のgh CLIだけが有効になります。

つまり、github.com への認証はキーチェーンを経由しない

認証情報の保管場所

macOS + gh CLI環境では、認証情報が以下の場所に存在し得ます。

保管場所保存するプログラムキーチェーンでの表示確認コマンド
キーチェーンgit(osxkeychain)github.com / Internet passwordキーチェーンアクセス.app
キーチェーンgh CLIgh:github.com / application passwordキーチェーンアクセス.app
gh CLI 内部gh CLIgh auth status

Internet password と application password

キーチェーンアクセスでgithub.comを検索すると、2種類のエントリが見つかることがあります。

  • Internet passwordgithub.com):git が osxkeychain helper 経由で保存したトークン
  • application passwordgh:github.com):gh CLI が保存したトークン

どちらも PATのキャッシュであり、原本はGitHub側にあります。キーチェーンから削除しても GitHub上のトークン自体は無効化されません。逆に、GitHub上でトークンを失効させても、キーチェーンのキャッシュは残り続けます(ただし認証は失敗します)。

osxkeychainの検索キーとuseHttpPath

osxkeychain helperは通常、ホスト名 + プロトコル(github.com + https)でエントリを検索します。リポジトリ単位ではありません。

ただし、.gitconfigcredential.useHttpPath=true が設定されている場合、リポジトリのパスまで含めてエントリが作られます。この設定があると、キーチェーンに大量のエントリが生まれる原因になります。

$ git config --get credential.useHttpPath
true  # この設定があるとリポジトリ単位でエントリが作られる

PAT の種類:ClassicとFine-grained

実はGitHubのPATには2種類あります、、!

ClassicFine-grained
トークンの prefixghp_github_pat_
権限の粒度スコープ単位(repo, workflow 等)リポジトリ + 権限の組み合わせ
リポジトリ制限全リポジトリにアクセス可指定したリポジトリのみ
有効期限任意(無期限も可)必須(最大1年)

Fine-grained tokenはリポジトリ単位でアクセスを制限できるため、セキュリティ面で推奨されています。ただし、gh auth loginで使う場合は Classic tokenのほうが設定がシンプルです。

なぜキーチェーンを消しても直らないのか

credential helperの優先順位を踏まえると、原因が見えてきます。

git push
  → credential.https://github.com.helper を参照
  → 空の helper で osxkeychain をリセット
  → gh auth git-credential が応答
  → gh CLI 内部の古いトークンを返す
  → 認証失敗

キーチェーンのInternet passwordを全削除しても、そもそもgithub.comへの認証でキーチェーンは参照されていません。gh CLIが応答しているので、gh CLI側のトークンを更新する必要があります。

実際に使われているトークンを確認する

$ printf "host=github.com\nprotocol=https" | git credential fill
protocol=https
host=github.com
username=your-username
password=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

ここで返されるpassword が、git pushで実際に使われるトークンです。GitHubの Token settings でこのトークンが有効か確認できます。

対処方法

gh CLIがcredential helperの場合(大半の環境)

$ gh auth login
? What account do you want to log into? GitHub.com
? What is your preferred protocol for Git operations? HTTPS
? How would you like to authenticate? Paste an authentication token
? Paste your authentication token: ****************************************
 Logged in as your-username

正しく更新されたか確認します。

$ gh auth status
github.com
 Logged in to github.com account your-username
 Git operations for github.com configured to use https protocol.
 Token: ghp_****
 Token scopes: 'gist', 'read:org', 'repo', 'workflow'

osxkeychainのみの場合

# 古いエントリを削除
$ printf "host=github.com\nprotocol=https\n" | git credential-osxkeychain erase

# 次回の git push でトークン入力を求められ、自動保存される

トークン期限切れ時の対処フロー

毎回迷わないために、以下の3ステップで対処します。

1. credential helperを確認する
   $ git config --show-origin --list | grep credential

2. 実際に使われているトークンを確認する
   $ printf "host=github.com\nprotocol=https" | git credential fill

3. 該当する helper のトークンを更新する
   - gh CLI → gh auth login
   - osxkeychain → credential-osxkeychain erase → git push で再入力

まとめ

  • Gitはcredential helperに認証を委任しており、自身ではトークンを管理しません
  • gh CLIが優先されている環境では、キーチェーンを削除しても認証は変わりません
  • git config --list | grep credential委任先を確認するのが最初の一手です
  • gh CLI 環境なら gh auth login の一行で解決します