11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHub App の Device Flowでスコープを絞った短命なGitHubトークンを生成する

11
Last updated at Posted at 2026-06-19

はじめに

GitHubの公式CLIであるghコマンドはべんりですが、非常に強い権限を持っているので攻撃する側から見ても魅力的です。特に gh auth token コマンドを実行するだけで簡単にGitHubトークンを抜けることは、もはや公然の秘密というか、みんな見て見ぬふりをしている大きな穴です。さらに都合の悪いことに、GitHubの公式CLIのOAuth Appで認証すると、このトークンには repo 権限があるので、自分がアクセス可能なプライベートリポジトリを読み書きでき、有効期限も設定されていないので、トークンが漏洩した場合の影響が大きくなりがちです。最近のマルウェアは個人のローカル端末を狙う攻撃が増えてきているので、GitHubトークンを抜かれた場合のダメージを如何に最小化するかというのは重要な課題です。

最初に思いつく代替案は、GitHub Fine-grained PATを使うことです。細かくスコープと有効期限を絞れるのでよい選択肢だと思いますが、トークンを生成するAPIが生えておらずローテーションが面倒です。人類は怠惰なので期限を長めに設定しがちです。また会社で管理しているGitHub orgにアクセスする場合は、orgの管理者側で有効期限の上限を設定したりスコープをレビューすることは可能なものの、逐一妥当性をチェックするのは管理者側の負担も無視できません。

GitHub Actionsから他のリポジトリのGitHub APIを叩いたことがある人なら、GitHub Appのインストレーショントークンを使うという案も思いつくかもしれません。しかしながらこの方法は、マシンユーザからのアクセスに使うことを想定しており、クライアントシークレットの管理が必要となることや、操作の主体がGitHub App=botユーザになってしまうので、複数の開発者がローカル端末で共有するのには不向きです。個人利用ならさておき、開発者の人数分だけAppを作ろうとするとスケールしません。

第3の選択肢として、GitHub App の Device Flowを使ってユーザアクセストークン(User-to-Server Token)を生成するという方法があります。この方法であればクライアントシークレットの管理も不要で、操作の主体もユーザになるので、開発者がローカル端末で使うのに都合がよいです。GitHub Appのユーザアクセストークンは有効期限が8時間で、スコープもOAuth Appよりも細かい粒度で絞れます。

この記事では、GitHub App の Device FlowでGitHubトークンを生成する方法について、curlを使った素朴な方法から始めて仕組みの理解を深めつつ、実務上ghコマンドやgitコマンドなどと組み合わせて使うのに便利なghtknというツールの使い方を補足します。

GitHub App の Device Flow とは

GitHub App の Device Flow とは、RFC8628: OAuth 2.0 Device Authorization Grant を使ってGitHubトークンを払い出す仕組みです。

OAuth 2.0のDevice Flowは、元々ブラウザにアクセスできないようなデバイスが、ユーザの認可を得てリソースにアクセスするための仕組みです。ここでいう「デバイス」というのは、たとえばネット接続可能なスマート家電のようなものを想定して設計されたものですが、OAuthクライアントであるデバイスは、ユーザのブラウザに直接アクセスできないで、ユーザに指定のURLにアクセスしてワンタイムパスワードを入力してもらうことで、間接的に許可を得て、認可サーバからアクセストークンを得ます。

以下は、RFC8628から抜粋したデバイスフローの概要図です。

      +----------+                                +----------------+
      |          |>---(A)-- Client Identifier --->|                |
      |          |                                |                |
      |          |<---(B)-- Device Code,      ---<|                |
      |          |          User Code,            |                |
      |  Device  |          & Verification URI    |                |
      |  Client  |                                |                |
      |          |  [polling]                     |                |
      |          |>---(E)-- Device Code       --->|                |
      |          |          & Client Identifier   |                |
      |          |                                |  Authorization |
      |          |<---(F)-- Access Token      ---<|     Server     |
      +----------+   (& Optional Refresh Token)   |                |
            v                                     |                |
            :                                     |                |
           (C) User Code & Verification URI       |                |
            :                                     |                |
            v                                     |                |
      +----------+                                |                |
      | End User |                                |                |
      |    at    |<---(D)-- End user reviews  --->|                |
      |  Browser |          authorization request |                |
      +----------+                                +----------------+

                    Figure 1: Device Authorization Flow

デバイスフローの流れをざっくり説明すると、

(A) クライアントは、クライアントIDを認可サーバに送信する。
(B) 認可サーバは、デバイスコード、ユーザコード、検証URLを返す。
(C) クライアントは、ユーザに「検証用URLにユーザコードを入力」するように依頼する。
(D) 認可サーバは、ユーザを認証し、ユーザコードを検証し、リクエストの許可を求める。
(E) クライアントは、クライアントIDとデバイスコードを認可サーバに送信し、ユーザの許可が得られるまでリトライする。
(F) 認可サーバは、クライアントIDとデバイスコードを検証し、ユーザの許可が得られればアクセストークンを返す。

で、話をGitHubに戻すと、このブラウザに直接アクセスできないクライアントデバイスというのが、GitHub APIを叩きたいアプリケーションで、CLIツールのようなヘッドレスで使うのに都合がよく、GitHub Appの認証方法としてDevice Flowを使うことができます。

念のため混同しないように補足しておきますが、OAuth 2.0のDevice Flow自体は手続きの流れを規定しているだけで、GitHubには「OAuth App」と「GitHub App」という似たような機能があり、どちらもDevice Flowが使えます。歴史的な経緯で(?)GitHub公式CLIは「OAuth App」としてDevice Flowを使っていますが、「OAuth App」はスコープの粒度が荒く、有効期限も設定できません。
一方、後発のGitHub Appはスコープを細かい粒度で指定でき、Device Flowで認証すると、有効期限が8時間だけのアクセストークンが得られます。GitHub AppにはDevice Flowの他に、独自プロトコルでインストレーショントークンを発行する方法もありますが、Device Flowを使ったユーザアクセストークンは操作の主体がユーザとなるので、ユーザが持っている権限以上のことはできず、個人がローカル端末で使うのに都合がよいです。用途に合わせて権限の異なるGitHub Appを複数作成することで、読み取り専用のトークンと書き込み可能なトークンを使い分けることもできます。

ちなみに有効期限8時間は固定で、現状は期限を変更する設定はありません。8時間を短命というにはちょっと長い気もしますが、Fine-grained PATの最小期限は1日ですし、OAuthだと無期限なことを考えれば最短です。トークンが漏洩した場合の影響を最小化するという観点では、現状一番マシな選択肢でしょう。

やってみた

事前準備

ghコマンドがログインした状態だと、稼働確認が分かりづらいので、 gh auth logout してから試すことをオススメします。

$ gh auth logout
$ gh auth status
You are not logged into any GitHub hosts. To log in, run: gh auth login

ghコマンドは環境変数 GH_TOKEN があればそれを優先的に見てくれるので、 gh auth login していなくても使えます。

GitHub Appの作成

適当なGitHub Appを作成します。個人設定の場合は以下にあります。
「Settings」 > 「Developer Settings」 > 「GitHub Apps」 > 「New GitHub App」
https://github.com/settings/apps

設定は以下のとおりとしました。ポイントは「Enable Device Flow」のチェックを入れることです。

  • GitHub App name: minamijoyo-ghdevice-none(適当な名前を付ける)
  • Homepage URL: https://github.com/minamijoyo (適当に埋めておく)
  • Identifying and authorizing users:
    • Expire user authorization tokens: on (デフォルトでチェックが付いてる)
    • Enable Device Flow: on(チェックを入れる)
  • Webhook:
    • Active: off(チェックを外す)
  • Permissions: (とりあえずデフォルトのまま何も付けない)
  • Where can this GitHub App be installed?: Only on this account (デフォルトのまま)

github.com_settings_apps_new.png

GitHub Appの名前についての補足ですが、インストール可能な範囲を「Only on this account」に制限しても、App名は公開されます。グローバルでユニークな名前にすることと、外から見られても問題ない名前にする必要があります。GitHub orgやEnterprise内にAppを作ってインストール範囲を制限した場合でも同じです。

Permissionsは検証のため、とりあえずデフォルトのまま何も付けていないですが、これだけでもpublicリポジトリにはアクセスできますし、レートリミット制限の緩和にもなるので、一応意味はあります。
他にもRead-only用のAppや、Write用のAppなど権限を分けたAppを用意するとよいでしょう。
一般的なコードを書く作業では、contents, issues, pull-requestsの読み書きがあれば足りることが多いと思うので、ここでは以下のような権限の異なるGitHub Appを用意しました。

  • minamijoyo-ghdevice-none: (権限なし)
  • minamijoyo-ghdevice-read:
    • contents:read
    • issues:read
    • pull-requests:read
    • metadata:read
  • minamijoyo-ghdevice-write:
    • contents:write
    • issues:write
    • pull-requests:write
    • metadata:read

補足として、GitHub Appになんらかの権限を付与すると、metadata:readの権限も自動で付いてきますが仕様です。actionsを叩きたいとか、他のAPIを使いたいケースはユースケースごとにAppを分けるとよいかもしれません。特に .github/workflows 配下のファイルをpushするのには、 contents:write だけでは足りず、別途 workflows:write 権限が必要です。Write用のAppの権限に混ぜるか、たまにしか使わないメンテナ権限をMaintain用のAppとして分けるかは、リポジトリの運用次第でよしなに判断して下さい。

GitHub Appを作成するとclient_idが払い出されます。以降で使うので控えておきます。
client_id自体はシークレットではありませんが、インストール範囲を制限したprivateなGitHub Appの場合は公開情報でもありません。client_idだけでアクセストークンは取得できませんが、社内で共有する場合は社内限の取り扱いが妥当かなと思います。

次にGitHub Appをインストールします。リポジトリ単位に設定するか、ユーザスコープ or orgスコープでまとめてインストールすることもできます。
「Settings」 > 「Developer Settings」 > 「GitHub Apps」から先ほど作成したAppを選択し、
https://github.com/settings/apps

ここではユーザスコープで全リポジトリにインストールします。
「Install App」 > 「All Repositories」 > 「Install」

補足として、GitHub App の Device Flowで払い出されるユーザアクセストークン(User-to-Server Token)は、GitHub Appの権限とユーザの権限の積集合の権限しか持っていないので、org内の全リポジトリに一括でインストールしても、ユーザがアクセスできないリポジトリにはアクセスできません。

逆に言うと、Appをインストールしていないリポジトリにもアクセスできないので、Appのインストールの有無で権限の違いを確認したい場合は、特定のリポジトリにだけインストールしてみて下さい。

curlでナマのAPIを叩いてみる

仕組みを理解しないと使えないタイプの人間なので、まずはcurlでナマのAPIを叩いてみます。

まず、ターミナルからcurlコマンドで https://github.com/login/device/code に client_id をPOSTすると、device_code と user_code と verification_uri が返ってきます。

$ curl -s -X POST https://github.com/login/device/code \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"client_id": "xxxx"}' \
  | jq .

{
  "device_code": "xxxx",
  "user_code": "XXXX-XXXX",
  "verification_uri": "https://github.com/login/device",
  "expires_in": 899,
  "interval": 5
}

次に、ブラウザで https://github.com/login/device を開いて、GitHubにログインして認証します。既にログイン済みの場合は、GitHubのアカウント選択画面が出るので、アカウントを選択します。

image.png

ユーザコードを入力する画面が出るので、先ほどのレスポンスにあったユーザコードを入力します。

image.png

「Authorize」ボタンを押して、認可リクエストを承認します。(この画面に要求するスコープの一覧が出ないのはちょっと微妙な仕様ですね)

image.png

ターミナルに戻って https://github.com/login/oauth/access_token に client_id と device_code をPOSTします。このとき、 grant_type に "urn:ietf:params:oauth:grant-type:device_code" を指定します。

$ curl -s -X POST https://github.com/login/oauth/access_token \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"client_id": "xxxx", "device_code": "xxxx", "grant_type": "urn:ietf:params:oauth:grant-type:device_code"}' \
  | jq .

{
  "access_token": "ghu_xxxx",
  "expires_in": 28800,
  "refresh_token": "ghr_xxxx",
  "refresh_token_expires_in": 15811200,
  "token_type": "bearer",
  "scope": ""
}

これで、レスポンスにaccess_tokenとrefresh_tokenが得られます。
expires_inの単位は秒なので、アクセストークンの期限は8時間、リフレッシュトークンの期限は183日です。リフレッシュトークンの期限がおよそ半年もあり、現状有効期限を変更できないのがちょっと気になりますが、リフレッシュトークンは使うと、古いアクセストークンとリフレッシュトークンは無効になります。

curlでこのアクセストークンが有効なことを確認してみましょう。

$ curl -s https://api.github.com/user \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Authorization: token ghu_xxxx" \
  | jq -r .login

minamijoyo

ghコマンドは環境変数 GH_TOKEN が設定されていればそれを使うので、アクセストークンを指定すれば使えます。

$  GH_TOKEN=ghu_xxxx gh issue list

gh issue list コマンドはpublicリポジトリであれば権限なしで叩けますが、privateリポジトリの場合は、事前に issues:read 権限を持ったGitHub Appを対象のリポジトリにインストールしておく必要があります。権限やリポジトリを変えてアクセス制御が意図したとおりに機能しているか試してみて下さい。

ここまで試せば分かると思いますが、この使い方ではGitHub Appというのはただの権限セットの定義でしかありません。なんらか常時起動するサーバが必要なわけでもありません。用途に合わせて複数用意して使い分けるとよいでしょう。

3rd-partyツールによるトークン生成

残念ながら本稿執筆時点では、任意のGitHub AppのDevice Flowのトークンを生成するGitHub公式のクライアント実装は存在しないので、3rd-partyツールの実装がいくつかあります。

私はプラグインマネージャにmiseを使っているので、mise v2026.05.06で実験的に追加されたGitHub AppのDevice Flowを使ったGitHubトークン生成機能が気になって試してみたのですが、本稿検証時点のmise v2026.6.10では、このトークンが ~/.local/state/mise/github-oauth-tokens.toml に平文で保存されてしまうという問題があり、まだexperimentalな扱いなので今後に期待です :expressionless:

以下ではちゃんとトークンを暗号化してOSのキーチェインに保存してくれるghtknを使ってみます。
3rd-partyは使えないポリシーの人は、アクセストークンの払い出し処理そのものはそんなに難しくないので、自作して車輪の再発明すればよいんじゃなかろうか。

ghtkn

ghtknは、GitHub AppのDevice Flowによるトークンの払い出しをしてくれるCLIツールです。単にアクセストークンを生成するだけではなく、Git Credential Helperとして振る舞うモードもあり、デフォルト設定でトークンは暗号化してOSのキーチェインに保存されます。

既に作者の@suzuki-shunsuke氏による解説記事があるので、そっちを読んでねで終わらせてよいんですが、

僭越ながら、私なりの使い方を以下でも簡単にまとめておきます。

インストール

ghtknはGo製のツールでビルド済みのバイナリがGitHub Releaseで配布されているので、いろいろな方法でインストール可能です。インストール方法は、公式ドキュメントを参照して下さい。
https://github.com/suzuki-shunsuke/ghtkn/blob/main/INSTALL.md

私はmiseで管理しているので、mise経由でインストールしました。

$ mise use -g ghtkn
$ ghtkn --version
ghtkn version 0.2.5

ghtknの設定

ghtkn init すると ~/.config/ghtkn/ghtkn.yaml に設定ファイルの雛形が生成されます。

$ ghtkn init

ここでは、GitHub Appを権限ごとに分けて作成し、それぞれのclient_idを登録します。

~/.config/ghtkn/ghtkn.yaml
# yaml-language-server: $schema=https://raw.githubusercontent.com/suzuki-shunsuke/ghtkn-go-sdk/refs/heads/main/json-schema/ghtkn.json
# ghtkn - https://github.com/suzuki-shunsuke/ghtkn
apps:
  - name: minamijoyo/none
    client_id: xxxx
  - name: minamijoyo/read
    client_id: xxxx
  - name: minamijoyo/write
    client_id: xxxx

ghtkn auth コマンドを実行するとブラウザが開くので、ユーザコードを入力して、アクセスを認可するとアクセストークンが払い出されます。

$ ghtkn auth minamijoyo/none
The application uses the device flow to generate your GitHub User Access Token.
Copy your one-time code: XXXX-XXXX
This code is valid until YYYY-MM-DDThh:mm:ss+09:00
Press Enter to open https://github.com/login/device in your browser (it opens automatically after 10 seconds)...

ghtkn get コマンドでトークンを取得します。

$ ENV GH_TOKEN=$(ghtkn get minamijoyo/none) gh issue list

ghコマンドをラップする

App名は引数でも指定できますが、環境変数 GHTKN_APP でも指定可能です。
direnvなどでディレクトリに応じて自動で GHTKN_APP を差し込む想定で、 適当なラッパースクリプトを書いてPATHを通しておくことで、 gh コマンドを差し替えます。

#!/usr/bin/env bash

set -eu

if [ -z "${GH_TOKEN:-}" ] && [ -z "${GITHUB_TOKEN:-}" ]; then
  GH_TOKEN="$(ghtkn get)"
  export GH_TOKEN
fi

exec $(mise which gh) "$@"

無限ループしないように注意して下さい。最後の行はインストール方法に依存しますが、絶対パスに読み替えてください。

Git Credential Helperとして使う

git コマンドをhttps経由で使っている場合、Git Credential Helperにもアクセストークンが埋まっています。これは普段あんまり意識していないかもしれませんが、 git credential fill コマンドを使うと簡単にトークンが抜けます。知ってましたか?気になる人は以下のコマンドを試してみて下さい。

$ git credential fill <<EOS
protocol=https
host=github.com

EOS
protocol=https
host=github.com
username=x-access-token
password=xxxx

Git Credential Helperをどのように設定しているかによりますが、GitHub公式のGCM(Git Credential Manager)のOAuthトークンを使っている場合、これも期限がないので差し替えます。

ghtknには ghtkn git-credential コマンドでGit Credential Helperのように振る舞うモードがありますが、前述の通りghコマンドをラッパーに差し替えて環境変数 GHTKN_APP を実行時に差し込む想定で、 gh auth git-credential コマンドを使うこともできます。

~/.gitconfig のcredentialセクションで以下のように設定します。

~/.gitconfig
[credential]
	helper =
	helper = !gh auth git-credential
	useHttpPath = true

ソースコードを ~/src/github.com/minamijoyo のようにorgの構造に合わせてcheckoutしている想定で、direnvを使って環境変数 GHTKN_APP を出し分ける作戦です。
~/src/github.com/minamijoyo/.envrc に以下のようなdirenvのデフォルト設定を書いておき、publicリポジトリはnoneとし、privateリポジトリはreadとします。
またwriteのときに必要な環境変数を適当な名前ここでは X_GHTKN_WRITE_APP に設定しておきます。

export GHTKN_APP=minamijoyo/none
export X_GHTKN_WRITE_APP=minamijoyo/write

で、git pushが必要なときだけ、GHTKN_APPを差し替えます。

ENV GHTKN_APP=$X_GHTKN_WRITE_APP git push origin HEAD

毎回指定するのは面倒なので、 ~/.gitconfig にaliasを書いておきます。

[alias]
  pH = "!f(){ ENV GHTKN_APP=$X_GHTKN_WRITE_APP git push origin HEAD; };f"
  pF = "!f(){ ENV GHTKN_APP=$X_GHTKN_WRITE_APP git push --force-with-lease --force-if-includes origin HEAD; };f"

この方法のメリットは、gitコマンドに使うトークンも、実行時に環境変数GHTKN_APP を上書きすることで出し分けし、pullだけであればreadを、pushが必要になるまではwriteのトークンを払い出さないことです。

他にもディレクトリベースで切り替えるだけであれば、gitconfigの includeIf で出し分ける方法や、 ~/.config/ghtkn/ghtkn.yaml に git_owner を指定する方法、forkにも対応可能なv0.2.6以降で環境変数 GHTKN_GIT_APP を使う方法があるようです。
各自で自分の開発のワークフローにあった方法を見つけてみて下さい。

ユーザコードをクリップボードにコピペする

ghtkn v0.3.1で ghtkn auth -p [GHTKN_APP] でユーザコードをクリップボードにコピペする機能が追加されました。
古いバージョンでのハックは折り畳んでおきます。

ユーザコードをクリップボードにコピペするエイリアス

アクセストークンの有効期限が8時間で短いのはよいことなのですが、トークンの払い出しはApp単位なので、複数のAppを使い分けていると、1日数回認証が必要になり、ユーザコードをコピペするのも煩雑です。
ghtkn authの出力をパースして、ユーザコードをコピーするワンライナーを作成しておくとべんりそうです。
素朴に実装すると以下のようなイメージになりますが、これは意図したとおりに機能しません。

$ ghtkn auth | grep -oE "[A-Z0-9]{4}-[A-Z0-9]{4}" | pbcopy

これがなぜうまくいかないかと言うと、 ghtkn auth コマンドはユーザコードを出力したのちに、アクセストークンが払い出されるまでフォアグラウンドでブロックしてしまうので、 pbcopy コマンドは入力を待ち続けてしまい、バッファをクリップボードにフラッシュしないからです。
いろいろ試行錯誤した結果、正解は以下のとおりです。

~/.zshrc
alias ghauth='(){ ghtkn auth "$@" 2>&1 | tee >(grep -oE "[A-Z0-9]{4}-[A-Z0-9]{4}" --line-buffered | head -n1 | tr -d "\n" | pbcopy) }'

一応解説しておくと、ghtkn auth はユーザコードを標準エラー出力に書き込むので、 tee コマンドと >() プロセス置換を組み合わせて出力を複製しつつ、 grep --line-buffered でバッファを1行単位に出力し、 head -n1 で1行だけ読んだら早期終了してパイプを閉じ、下流の pbcopy にこれ以上入力がないことを伝えます。 tr -d "\n" で改行を取り除くのは、改行が入ってるとGitHub側のユーザコード入力画面に貼り付けができないからです。

これをzshのエイリアスや関数として ~/.zshrc に仕込んでおくと、ユーザコードがクリップボードにコピーされた状態でブラウザが立ち上がります。あとは貼り付けるだけの簡単なお仕事。

$ ghauth minamijoyo/none

認証するGitHub Appをfzfで選択する

権限ごとに複数のAppを使い分けていると、GHTKN_APPをいいかんじに補完したくなります。
というわけで、ghtkn.yamlをyqでパースしてGHTKN_APPを一覧化し、fzfでインクリメンタルに選択できるショートカットなどを仕込んでおくと捗ります。bindkeyは好きなキーに割り当てて下さい。

~/.zshrc
function fzf-ghtkn-auth() { ghtkn auth -p $(yq '.apps[].name' "$HOME/.config/ghtkn/ghtkn.yaml" | sort | fzf) }
zle -N fzf-ghtkn-auth
bindkey '^r^t' fzf-ghtkn-auth

リフレッシュトークンを使う

ghtkn v0.3.4からリフレッシュトークンが使えるようになりました。これにより8時間ごとにユーザコードを入力する必要がなくなります。

リフレッシュトークンを使うには、ghtknをbackendモードagentで動かす必要があります。ここでagentとは常駐するデーモンプロセスのことです。
まず ~/.config/ghtkn/ghtkn.yaml にbackendの設定を追加します。

~/.config/ghtkn/ghtkn.yaml
# yaml-language-server: $schema=https://raw.githubusercontent.com/suzuki-shunsuke/ghtkn-go-sdk/refs/heads/main/json-schema/ghtkn.json
# ghtkn - https://github.com/suzuki-shunsuke/ghtkn
backend:
  type: agent
apps:
  - name: minamijoyo/none
    client_id: xxxx
  - name: minamijoyo/read
    client_id: xxxx
  - name: minamijoyo/write
    client_id: xxxx

つぎにagentを起動します。

$ ghtkn agent start

このコマンドはフォアグラウンドでブロックしてしまうので、もう1枚ターミナルを開いて、リフレッシュモードを有効化しつつ、パスフレーズを設定します。

$ ghtkn agent unlock --enable-refresh
Enter the agent passphrase:

あとはいつもどおり ghtkn auth で認証して、初回はユーザコードを入力し、 ghtkn get でアクセストークンを取得します。

$ ghtkn auth -p minamijoyo/none
$ ghtkn get minamijoyo/none
ghu_xxxx

リフレッシュ動作を確認するには、 ghtkn get -m 8h としてアクセストークンの有効期限の下限を指定することで、現在のアクセストークンを有効期限切れと判定させてリフレッシュを強制できます。このときブラウザでユーザコードの再入力を求められず、アクセストークンの値が変わっていればリフレッシュできています。

$ ghtkn get -m 8h minamijoyo/none
ghu_yyyy

以下、いくつか知っておくべきことの補足など。
GitHub App の Device Flowの仕様で、リフレッシュトークンの有効期限は6ヶ月と長めかつ設定変更不可なのは、やはり気になるポイントです。一応リフレッシュトークンは一度使うとアクセストークンだけではなく新しいリフレッシュトークンが発行され、古いものは再利用不可となるので、無限に有効なリフレッシュトークンが増えるわけではありません。
https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/refreshing-user-access-tokens

The user access token expires after eight hours, and the refresh token expires after six months.
(snip.)
You can use the refresh token to generate a new user access token and a new refresh token. Once you use a refresh token, that refresh token and the old user access token will no longer work.

ghtknではリフレッシュトークンをパスフレーズで暗号化して保存し、リフレッシュトークンそのものを取り出す方法が提供されていないので、有効期限の長いFine-grained PATを払い出すよりは安全です。ghコマンドなどに渡すアクセストークンの期限は8時間のままなので。

暗号化されたトークンはファイルとして ~/.cache/ghtkn/agent/ 配下にクライアントIDごとに保存されており、しばらくリフレッシュされていない未使用のものは定期的にゴミ消しする仕組みが入っています。ゴミ消しの間隔は ghtkn agent unlock --refresh-token-ttl で調整可能で、デフォルト値は1w(=1週間)に設定されています。つまり、1週間リフレッシュされていないものは消されて、次回Device Flowのユーザコード入力が求められます。これはリフレッシュトークンそのもののRevokeではありませんが、ローカル端末が侵害された場合に使用頻度の低いトークンが漏洩する可能性を減らし、被害拡大を防ぐリスクヘッジとしては意味があります。

ちなみにGitHubにはアクセストークンやリフレッシュトークンをRevokeするAPIも生えているのですが、試しにこのAPIにトークンを投げ込むと、GitHubからトークンが流出したかもという警告メールが届くような仕様になっており、あくまでもインシデント対応用であって、普段遣いするものではなさそうです。自動のゴミ消し処理の都度このメールが来るのはノイズで、本当のインシデントを見逃しそうなので、ローカルから未使用トークンだけ消すのは、現状の技術的な制限を考慮すると妥当な落としどころかなと思ってます。

不要になった公式のOAuth Appを無効化する

最後に不要になった公式のOAuth Appは忘れずに無効化しておきましょう。

「Settings」 > 「Applications」
https://github.com/settings/applications

「Authorized OAuth Apps」のタブで、「GitHub CLI」をRevokeします。
「Git Credential Manager」もOAuth Appで使っていたのであれば、同様にRevokeします。

まとめ

GitHub App の Device Flowでスコープを絞った短命なGitHubトークンを生成する方法について説明しました。
GitHub公式でもうちょっといいかんじになんとかして欲しい気持ちはありつつ、現状の技術的な制約の元で一番マシそうな解決策だと思うのでお試し下さい。
世界が少しでもセキュアになりますように :pray:

11
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?