Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ rattler_virtual_packages = { version = "2", default-features = false }
regex = "1"
reqwest = { version = "0.13", default-features = false, features = [
"json",
"form",
"gzip",
"zstd",
"charset",
Expand Down
2 changes: 1 addition & 1 deletion docs/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ Can also use `MISE_NO_HOOKS=1`
- [`mise test-tool [FLAGS] [TOOLS]…`](/cli/test-tool.md)
- [`mise token <SUBCOMMAND>`](/cli/token.md)
- [`mise token forgejo [--unmask] [HOST]`](/cli/token/forgejo.md)
- [`mise token github [--unmask] [HOST]`](/cli/token/github.md)
- [`mise token github [FLAGS] [HOST]`](/cli/token/github.md)
- [`mise token gitlab [--unmask] [HOST]`](/cli/token/gitlab.md)
- [`mise tool [FLAGS] <TOOL>`](/cli/tool.md)
- [`mise tool-stub <FILE> [ARGS]…`](/cli/tool-stub.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/cli/token.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Display git provider tokens mise will use
## Subcommands

- [`mise token forgejo [--unmask] [HOST]`](/cli/token/forgejo.md)
- [`mise token github [--unmask] [HOST]`](/cli/token/github.md)
- [`mise token github [FLAGS] [HOST]`](/cli/token/github.md)
- [`mise token gitlab [--unmask] [HOST]`](/cli/token/gitlab.md)
10 changes: 9 additions & 1 deletion docs/cli/token/github.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!-- @generated by usage-cli from usage spec -->
# `mise token github`

- **Usage**: `mise token github [--unmask] [HOST]`
- **Usage**: `mise token github [FLAGS] [HOST]`
- **Source code**: [`src/cli/token/github.rs`](https://github.com/jdx/mise/blob/main/src/cli/token/github.rs)

GitHub token
Expand All @@ -16,6 +16,14 @@ GitHub hostname

## Flags

### `--oauth`

[experimental] Resolve only via the native GitHub OAuth source (cache, refresh, or device-code flow), bypassing other token sources

### `--raw`

Print only the token value

### `--unmask`

Show the full unmasked token
Expand Down
80 changes: 65 additions & 15 deletions docs/dev-tools/github-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ mise checks the following sources in order. The first token found wins:

**github.com:**

| Priority | Source |
| -------- | ---------------------------------- |
| 1 | `MISE_GITHUB_TOKEN` env var |
| 2 | `GITHUB_API_TOKEN` env var |
| 3 | `GITHUB_TOKEN` env var |
| 4 | `credential_command` (if set) |
| 5 | `github_tokens.toml` (per-host) |
| 6 | gh CLI token (from `hosts.yml`) |
| 7 | `git credential fill` (if enabled) |
| Priority | Source |
| -------- | ----------------------------------- |
| 1 | `MISE_GITHUB_TOKEN` env var |
| 2 | `GITHUB_API_TOKEN` env var |
| 3 | `GITHUB_TOKEN` env var |
| 4 | `credential_command` (if set) |
| 5 | native GitHub OAuth (if configured) |
| 6 | `github_tokens.toml` (per-host) |
| 7 | gh CLI token (from `hosts.yml`) |
| 8 | `git credential fill` (if enabled) |

**GitHub Enterprise hosts:**

Expand All @@ -25,9 +26,10 @@ mise checks the following sources in order. The first token found wins:
| 1 | `MISE_GITHUB_ENTERPRISE_TOKEN` env var |
| 2 | `MISE_GITHUB_TOKEN` / `GITHUB_API_TOKEN` / `GITHUB_TOKEN` env vars |
| 3 | `credential_command` (if set) |
| 4 | `github_tokens.toml` (per-host) |
| 5 | gh CLI token (from `hosts.yml`, matched by hostname) |
| 6 | `git credential fill` (if enabled) |
| 4 | native GitHub OAuth (if configured) |
| 5 | `github_tokens.toml` (per-host) |
| 6 | gh CLI token (from `hosts.yml`, matched by hostname) |
| 7 | `git credential fill` (if enabled) |

::: tip
The github.com env vars (`MISE_GITHUB_TOKEN`, etc.) are also used as a fallback for GHE when `MISE_GITHUB_ENTERPRISE_TOKEN` is not set. If you need different tokens for github.com and a GHE instance, set `MISE_GITHUB_ENTERPRISE_TOKEN` explicitly or use the gh CLI integration.
Expand Down Expand Up @@ -136,6 +138,53 @@ Use `mise token github` to confirm mise can resolve the token:
mise token github
```

## Native GitHub OAuth <Badge type="warning" text="experimental" />

mise can create short-lived GitHub App user access tokens directly with GitHub's OAuth device flow. This does not require a personal access token, GitHub App private key, app client secret, `gh`, `ghtkn`, or any other external credential command.

The design was inspired by [ghtkn](https://github.com/suzuki-shunsuke/ghtkn) — if you'd rather run a separate process and have mise pick up its token via `credential_command`, see [Using ghtkn](#using-ghtkn) above.

::: warning
This feature is experimental. Enable it with `mise settings experimental=true` (or `MISE_EXPERIMENTAL=1`) before using it. Behavior, settings, and token cache format may change in future releases.
:::

Create a GitHub App with device flow enabled, then configure its client ID:

```sh
mise settings set experimental true
mise settings set github.oauth_client_id Iv1.yourgithubappclientid
```

Authorize once:

```sh
mise token github --oauth
```

After that, mise reuses the cached token for its own GitHub API calls and refreshes it when GitHub returns a refresh token. While the cached token is valid, mise also exports it to your shell as `GITHUB_TOKEN` (via `mise activate` / `mise hook-env` / `mise env` / `mise exec`) so tools like `gh`, `git`, and `cargo publish` see it without any extra wiring:

```sh
gh pr list # uses the OAuth token automatically
```

To use a different variable name (for example, `gh`'s preferred `GH_TOKEN`), set `github.oauth_export_env`. Setting it to an empty string disables the auto-export.

You can still print a raw token explicitly when you need to pipe it somewhere:

```sh
export MISE_GITHUB_TOKEN="$(mise token github --oauth --raw)"
```

Optional settings:

```toml
[settings.github]
oauth_client_id = "Iv1.yourgithubappclientid"
oauth_scopes = "" # usually empty for GitHub App user access tokens
oauth_open_browser = true
oauth_export_env = "GITHUB_TOKEN" # set to "" to disable automatic export
```

## Git Credential Helpers

mise can use your existing git credential helpers to obtain GitHub tokens. This is **opt-in** and acts as a last-resort fallback after all other token sources.
Expand Down Expand Up @@ -179,9 +228,10 @@ For authentication, mise checks (in order):
1. `MISE_GITHUB_ENTERPRISE_TOKEN` env var
2. `MISE_GITHUB_TOKEN` / `GITHUB_API_TOKEN` / `GITHUB_TOKEN` env vars
3. `credential_command` for the API hostname
4. `github_tokens.toml` for the API hostname
5. gh CLI token for the API hostname
6. `git credential fill` for the API hostname
4. native GitHub OAuth for the configured API hostname
5. `github_tokens.toml` for the API hostname
6. gh CLI token for the API hostname
7. `git credential fill` for the API hostname

If you have **multiple** GHE instances, `MISE_GITHUB_ENTERPRISE_TOKEN` (a single value) won't work. Use `github_tokens.toml`, the gh CLI integration, `credential_command`, or git credential helpers instead:

Expand Down
94 changes: 0 additions & 94 deletions e2e/cli/test_github_token

This file was deleted.

51 changes: 49 additions & 2 deletions e2e/cli/test_token_github
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ unset GITHUB_TOKEN GITHUB_API_TOKEN MISE_GITHUB_TOKEN MISE_GITHUB_ENTERPRISE_TOK
# Test 1: No token configured — should return (none)
assert_contains "mise token github" "(none)"

# --raw with no token should exit non-zero (so $(...) capture fails loudly)
assert_fail "mise token github --raw"

# Test 2: GITHUB_TOKEN env var
assert_contains "GITHUB_TOKEN=env-test-token mise token github --unmask" "env-test-token"
assert_contains "GITHUB_TOKEN=env-test-token mise token github --unmask" "GITHUB_TOKEN"
Expand Down Expand Up @@ -73,7 +76,51 @@ assert_contains "GITHUB_TOKEN=env-wins MISE_GITHUB_CREDENTIAL_COMMAND='echo cred
# Test 8e: credential_command receives host as $1
assert_contains "MISE_GITHUB_CREDENTIAL_COMMAND='echo token-for-\$1' mise token github ghe.example.com --unmask" "token-for-ghe.example.com"

# Test 9: git credential fill
# Test 9a: OAuth configured but no cached token — must not prompt for device flow
# during normal token resolution (would hang on the unreachable URL otherwise).
NO_DEVICE_OAUTH="MISE_EXPERIMENTAL=1 MISE_GITHUB_OAUTH_CLIENT_ID=Iv1.mock MISE_GITHUB_OAUTH_OPEN_BROWSER=false MISE_GITHUB_OAUTH_AUTH_URL=http://127.0.0.1:1/login/oauth MISE_GITHUB_OAUTH_API_URL=http://127.0.0.1:1/api"
assert_contains "$NO_DEVICE_OAUTH mise token github 127.0.0.1" "(none)"

# OAuth is experimental — calling --oauth without experimental enabled must fail
assert_fail "MISE_GITHUB_OAUTH_CLIENT_ID=Iv1.mock mise token github --oauth"

# Test 9: native GitHub OAuth device flow via token alias
MOCK_PORT_FILE="$HOME/mock-github-oauth-port"
MOCK_GITHUB_OAUTH_PORT_FILE="$MOCK_PORT_FILE" python3 "$ROOT/e2e/fixtures/mock-github-oauth.py" &
MOCK_PID=$!
for _ in $(seq 1 30); do
if [ -f "$MOCK_PORT_FILE" ]; then
break
fi
sleep 0.1
done
MOCK_PORT="$(cat "$MOCK_PORT_FILE")"
OAUTH_ENV="MISE_EXPERIMENTAL=1 MISE_GITHUB_OAUTH_CLIENT_ID=Iv1.mock MISE_GITHUB_OAUTH_OPEN_BROWSER=false MISE_GITHUB_OAUTH_AUTH_URL=http://127.0.0.1:$MOCK_PORT/login/oauth MISE_GITHUB_OAUTH_API_URL=http://127.0.0.1:$MOCK_PORT/api"
assert_contains "$OAUTH_ENV mise token github 127.0.0.1 --oauth --raw" "ghu-native-oauth-token"
assert_contains "$OAUTH_ENV mise token github 127.0.0.1 --unmask" "GitHub OAuth"

# Test 9b: cached OAuth token is auto-exported as GITHUB_TOKEN
assert_contains "$OAUTH_ENV mise env --shell bash" "export GITHUB_TOKEN=ghu-native-oauth-token"

# Test 9c: oauth_export_env can pick a different variable name
assert_contains "$OAUTH_ENV MISE_GITHUB_OAUTH_EXPORT_ENV=GH_TOKEN mise env --shell bash" "export GH_TOKEN=ghu-native-oauth-token"

# Test 9d: empty oauth_export_env disables auto-export
assert_not_contains "$OAUTH_ENV MISE_GITHUB_OAUTH_EXPORT_ENV= mise env --shell bash" "ghu-native-oauth-token"

# Test 9e: an explicit GITHUB_TOKEN in [env] must take precedence over the OAuth-injected token
mkdir -p "$MISE_CONFIG_DIR"
cat >mise.toml <<'EOF'
[env]
GITHUB_TOKEN = "user-set-token"
EOF
assert_contains "$OAUTH_ENV mise env --shell bash" "export GITHUB_TOKEN=user-set-token"
assert_not_contains "$OAUTH_ENV mise env --shell bash" "ghu-native-oauth-token"
rm mise.toml

kill "$MOCK_PID"

# Test 10: git credential fill

# Create a standalone credential helper script
cat >"$HOME/git-cred-helper" <<'SCRIPT'
Expand All @@ -90,5 +137,5 @@ GIT_CRED_ENV="GIT_CONFIG_NOSYSTEM=1 MISE_GITHUB_USE_GIT_CREDENTIALS=true"
assert_contains "$GIT_CRED_ENV mise token github --unmask" "git-cred-token"
assert_contains "$GIT_CRED_ENV mise token github --unmask" "git credential fill"

# Test 10: git credential disabled via setting
# Test 11: git credential disabled via setting
assert_contains "GIT_CONFIG_NOSYSTEM=1 MISE_GITHUB_USE_GIT_CREDENTIALS=false mise token github" "(none)"
Loading
Loading