From 9df13655cc7a97dab75c761ed4ecb346c6ef6e6a Mon Sep 17 00:00:00 2001 From: "E-Love (Eric Loveland)" Date: Thu, 16 Jul 2026 14:49:39 -0400 Subject: [PATCH 1/2] Add --client-id flag as preferred over --app-id Per https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app#generating-a-json-web-token-jwt, GitHub now recommends using the client ID as the JWT `iss` claim instead of the app ID. Introduce `--client-id` as a new flag with `--app-id` supported for backwards- compatibility. If both are provided, `--client-id` is used. --- README.md | 18 +++-- internal/generate.go | 18 ++++- internal/generate_flags.go | 10 ++- internal/generate_test.go | 126 +++++++++++++++++++++++++++----- internal/installations.go | 8 +- internal/installations_flags.go | 10 ++- internal/installations_test.go | 67 ++++++++--------- internal/key.go | 4 +- 8 files changed, 190 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 775da17..11cc76f 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ Follow [these steps](https://docs.github.com/en/developers/apps/creating-a-githu Compatible with [GitHub Enterprise Server](https://github.com/enterprise). +[GitHub recommends](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app#generating-a-json-web-token-jwt) using your app's **client ID** (rather than the numeric app ID) when authenticating. Both values are accepted by the GitHub API and can be passed via the `--client-id` (preferred) or `--app-id` flag. If both are provided, `--client-id` is used. + ```text NAME: gh-token - Manage GitHub App installation tokens @@ -92,7 +94,7 @@ GLOBAL OPTIONS: ```shell gh token generate \ --key ./.keys/private-key.pem \ - --app-id 1122334 \ + --client-id Iv23aBcD9eFgH1jKlMnO \ --installation-id 5566778 ``` @@ -115,7 +117,7 @@ gh token generate \ ```shell gh token generate \ --base64-key $(printf "%s" $APP_KEY | base64) \ - --app-id 1122334 \ + --client-id Iv23aBcD9eFgH1jKlMnO \ --installation-id 5566778 ``` @@ -138,7 +140,7 @@ gh token generate \ ```shell gh token generate \ --base64-key $(printf "%s" $APP_KEY | base64) \ - --app-id 1122334 \ + --client-id Iv23aBcD9eFgH1jKlMnO \ --installation-id 5566778 \ --hostname "github.example.com" ``` @@ -162,7 +164,7 @@ gh token generate \ ```shell gh token installations \ --key ./private-key.pem \ - --app-id 2233445 + --client-id Iv23aBcD9eFgH1jKlMnO ```
@@ -246,7 +248,7 @@ Successfully revoked installation token 1. You need to create a secret to store the **applications private key** securely (this can be an organization or a repository secret): ![Create private key secret](images/create_secret.png) -1. You need to create another secret to store the **application id** security (same as the step above). +1. You need to create another secret to store the **client ID** ([GitHub recommended](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/making-authenticated-api-requests-with-a-github-app-in-a-github-actions-workflow#authenticating-with-a-github-app)), or app ID security (same as the step above). 1. The secrets need to be provided as an environment variable then encoded into base64 as show in the workflow example: @@ -266,19 +268,19 @@ jobs: steps: - name: "Install gh-token" run: gh extension install Link-/gh-token - # Create access token with a GitHub App ID and Key + # Create access token with a GitHub App Client ID and Key # We use the private key stored as a secret and encode it into base64 # before passing it to gh-token - name: "Create access token" run: | token=$(gh token generate \ --base64-key $(printf "%s" "$APP_PRIVATE_KEY" | base64 -w 0) \ - --app-id $APP_ID \ + --client-id $APP_CLIENT_ID \ --hostname "github.example.com" \ | jq -r ".token") echo "token=$token" >> $GITHUB_OUTPUT env: - APP_ID: ${{ secrets.APP_ID }} + APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }} APP_PRIVATE_KEY: ${{ secrets.APP_KEY }} # To test the token we will use it to fetch the list of repositories # belonging to our organization diff --git a/internal/generate.go b/internal/generate.go index a94a689..7893f93 100644 --- a/internal/generate.go +++ b/internal/generate.go @@ -15,7 +15,10 @@ import ( // Generate is the entrypoint for the generate command func Generate(c *cli.Context) error { - appID := c.String("app-id") + iss, err := resolveIss(c) + if err != nil { + return err + } installationID := c.String("installation-id") keyPath := c.String("key") keyBase64 := c.String("base64-key") @@ -42,7 +45,6 @@ func Generate(c *cli.Context) error { jwtExpiry = 10 } - var err error var privateKey *rsa.PrivateKey if keyPath != "" { privateKey, err = readKey(keyPath) @@ -56,7 +58,7 @@ func Generate(c *cli.Context) error { } } - jsonWebToken, err := generateJWT(appID, jwtExpiry, privateKey) + jsonWebToken, err := generateJWT(iss, jwtExpiry, privateKey) if err != nil { return fmt.Errorf("failed generating JWT: %w", err) } @@ -97,6 +99,16 @@ func Generate(c *cli.Context) error { return nil } +func resolveIss(c *cli.Context) (string, error) { + if clientID := c.String("client-id"); clientID != "" { + return clientID, nil + } + if appID := c.String("app-id"); appID != "" { + return appID, nil + } + return "", fmt.Errorf("either --client-id or --app-id must be specified") +} + func retrieveDefaultInstallationID(hostname, jwt string) (string, error) { endpoint := fmt.Sprintf("https://%s/app/installations?per_page=1", hostname) req, err := http.NewRequest("GET", endpoint, nil) diff --git a/internal/generate_flags.go b/internal/generate_flags.go index e2c09b4..f6bd3ae 100644 --- a/internal/generate_flags.go +++ b/internal/generate_flags.go @@ -5,10 +5,16 @@ import "github.com/urfave/cli/v2" // GenerateFlags returns the CLI flags for the generate command func GenerateFlags() []cli.Flag { return []cli.Flag{ + &cli.StringFlag{ + Name: "client-id", + Usage: "GitHub App client ID (preferred)", + Required: false, + Aliases: []string{"client_id"}, + }, &cli.StringFlag{ Name: "app-id", - Usage: "GitHub App ID", - Required: true, + Usage: "GitHub App app ID", + Required: false, Aliases: []string{"i", "app_id"}, }, &cli.StringFlag{ diff --git a/internal/generate_test.go b/internal/generate_test.go index 3ced571..fe04928 100644 --- a/internal/generate_test.go +++ b/internal/generate_test.go @@ -22,6 +22,7 @@ func createTestContext(flags map[string]interface{}) *cli.Context { // Set default values defaults := map[string]interface{}{ + "client-id": "", "app-id": "", "installation-id": "", "key": "", @@ -91,7 +92,7 @@ func TestGenerate(t *testing.T) { { name: "successful_token_generation_with_key_file", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "installation-id": "12345", "key": "fixtures/test-private-key.test.pem", "hostname": "api.github.com", @@ -107,7 +108,7 @@ func TestGenerate(t *testing.T) { { name: "successful_token_generation_with_base64_key", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "installation-id": "12345", "base64-key": keyBase64, "hostname": "api.github.com", @@ -123,7 +124,7 @@ func TestGenerate(t *testing.T) { { name: "successful_with_auto_installation_id", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "key": "fixtures/test-private-key.test.pem", "hostname": "api.github.com", "jwt-expiry": 10, @@ -140,6 +141,43 @@ func TestGenerate(t *testing.T) { { name: "successful_jwt_only", flags: map[string]interface{}{ + "client-id": "123456", + "key": "fixtures/test-private-key.test.pem", + "jwt": true, + "jwt-expiry": 10, + "silent": true, + }, + setupMocks: func() {}, + expectedError: "", + }, + { + name: "error_no_app_identifier_specified", + flags: map[string]interface{}{ + "key": "fixtures/test-private-key.test.pem", + }, + setupMocks: func() {}, + expectedError: "either --client-id or --app-id must be specified", + }, + { + name: "successful_token_generation_with_app_id", + flags: map[string]interface{}{ + "app-id": "123456", + "installation-id": "12345", + "key": "fixtures/test-private-key.test.pem", + "hostname": "api.github.com", + "jwt-expiry": 10, + "silent": true, + }, + setupMocks: func() { + httpmock.RegisterResponder("POST", "https://api.github.com/app/installations/12345/access_tokens", + httpmock.NewStringResponder(201, string(tokenJSON))) + }, + expectedError: "", + }, + { + name: "prefers_client_id_when_both_specified", + flags: map[string]interface{}{ + "client-id": "Iv23aBcD9eFgH1jKlMnO", "app-id": "123456", "key": "fixtures/test-private-key.test.pem", "jwt": true, @@ -152,7 +190,7 @@ func TestGenerate(t *testing.T) { { name: "error_no_key_specified", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", }, setupMocks: func() {}, expectedError: "either --key or --base64-key must be specified", @@ -160,7 +198,7 @@ func TestGenerate(t *testing.T) { { name: "error_both_keys_specified", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "key": "fixtures/test-private-key.test.pem", "base64-key": keyBase64, }, @@ -170,8 +208,8 @@ func TestGenerate(t *testing.T) { { name: "error_invalid_key_file", flags: map[string]interface{}{ - "app-id": "123456", - "key": "fixtures/nonexistent.pem", + "client-id": "123456", + "key": "fixtures/nonexistent.pem", }, setupMocks: func() {}, expectedError: "unable to read key file", @@ -179,7 +217,7 @@ func TestGenerate(t *testing.T) { { name: "error_invalid_base64_key", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "base64-key": "invalid-base64-string", }, setupMocks: func() {}, @@ -188,8 +226,8 @@ func TestGenerate(t *testing.T) { { name: "error_installation_not_found", flags: map[string]interface{}{ - "app-id": "123456", - "key": "fixtures/test-private-key.test.pem", + "client-id": "123456", + "key": "fixtures/test-private-key.test.pem", }, setupMocks: func() { httpmock.RegisterResponder("GET", "https://api.github.com/app/installations?per_page=1", @@ -200,7 +238,7 @@ func TestGenerate(t *testing.T) { { name: "error_token_generation_fails", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "installation-id": "12345", "key": "fixtures/test-private-key.test.pem", }, @@ -398,7 +436,7 @@ func TestGenerateAdvancedCases(t *testing.T) { name: "jwt_expiry_below_minimum", setupTest: func() *cli.Context { return createTestContext(map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "key": "fixtures/test-private-key.test.pem", "jwt-expiry": 0, // Below minimum, should be adjusted to 10 "jwt": true, @@ -412,7 +450,7 @@ func TestGenerateAdvancedCases(t *testing.T) { name: "jwt_expiry_above_maximum", setupTest: func() *cli.Context { return createTestContext(map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "key": "fixtures/test-private-key.test.pem", "jwt-expiry": 15, // Above maximum, should be adjusted to 10 "jwt": true, @@ -426,7 +464,7 @@ func TestGenerateAdvancedCases(t *testing.T) { name: "hostname_without_api_path", setupTest: func() *cli.Context { return createTestContext(map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "installation-id": "12345", "key": "fixtures/test-private-key.test.pem", "hostname": "github.company.com", // Without /api/v3 @@ -448,7 +486,7 @@ func TestGenerateAdvancedCases(t *testing.T) { name: "hostname_with_api_path_already_included", setupTest: func() *cli.Context { return createTestContext(map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "installation-id": "12345", "key": "fixtures/test-private-key.test.pem", "hostname": "github.company.com/api/v3", // Already has /api/v3 @@ -505,7 +543,7 @@ func TestGenerateWithOutputFormats(t *testing.T) { { name: "json_output_format", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "installation-id": "12345", "key": "fixtures/test-private-key.test.pem", "hostname": "api.github.com", @@ -520,7 +558,7 @@ func TestGenerateWithOutputFormats(t *testing.T) { { name: "token_only_output_format", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "installation-id": "12345", "key": "fixtures/test-private-key.test.pem", "hostname": "api.github.com", @@ -536,7 +574,7 @@ func TestGenerateWithOutputFormats(t *testing.T) { { name: "jwt_output_format", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "key": "fixtures/test-private-key.test.pem", "jwt": true, "jwt-expiry": 10, @@ -564,3 +602,55 @@ func TestGenerateWithOutputFormats(t *testing.T) { }) } } + +func TestResolveIss(t *testing.T) { + tests := []struct { + name string + flags map[string]interface{} + expectedIss string + expectedError string + }{ + { + name: "client_id_only", + flags: map[string]interface{}{ + "client-id": "Iv23aBcD9eFgH1jKlMnO", + }, + expectedIss: "Iv23aBcD9eFgH1jKlMnO", + }, + { + name: "app_id_only", + flags: map[string]interface{}{ + "app-id": "123456", + }, + expectedIss: "123456", + }, + { + name: "prefers_client_id", + flags: map[string]interface{}{ + "client-id": "Iv23aBcD9eFgH1jKlMnO", + "app-id": "123456", + }, + expectedIss: "Iv23aBcD9eFgH1jKlMnO", + }, + { + name: "neither_specified", + flags: map[string]interface{}{}, + expectedError: "either --client-id or --app-id must be specified", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + iss, err := resolveIss(createTestContext(tt.flags)) + + if tt.expectedError != "" { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.expectedError) + assert.Equal(t, "", iss) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expectedIss, iss) + } + }) + } +} diff --git a/internal/installations.go b/internal/installations.go index e42039c..fe11f36 100644 --- a/internal/installations.go +++ b/internal/installations.go @@ -15,7 +15,10 @@ import ( // Installations is the entrypoint for the installations command func Installations(c *cli.Context) error { - appID := c.String("app-id") + iss, err := resolveIss(c) + if err != nil { + return err + } keyPath := c.String("key") keyBase64 := c.String("base64-key") hostname := strings.ToLower(c.String("hostname")) @@ -33,7 +36,6 @@ func Installations(c *cli.Context) error { hostname = strings.TrimSuffix(endpoint, "/") } - var err error var privateKey *rsa.PrivateKey if keyPath != "" { privateKey, err = readKey(keyPath) @@ -47,7 +49,7 @@ func Installations(c *cli.Context) error { } } - jsonWebToken, err := generateJWT(appID, 1, privateKey) + jsonWebToken, err := generateJWT(iss, 1, privateKey) if err != nil { return fmt.Errorf("failed generating JWT: %w", err) } diff --git a/internal/installations_flags.go b/internal/installations_flags.go index 96e7783..ee7dafe 100644 --- a/internal/installations_flags.go +++ b/internal/installations_flags.go @@ -5,10 +5,16 @@ import "github.com/urfave/cli/v2" // InstallationsFlags returns the CLI flags for the generate command func InstallationsFlags() []cli.Flag { return []cli.Flag{ + &cli.StringFlag{ + Name: "client-id", + Usage: "GitHub App client ID (preferred)", + Required: false, + Aliases: []string{"client_id"}, + }, &cli.StringFlag{ Name: "app-id", - Usage: "GitHub App ID", - Required: true, + Usage: "GitHub App app ID", + Required: false, Aliases: []string{"i", "app_id"}, }, &cli.StringFlag{ diff --git a/internal/installations_test.go b/internal/installations_test.go index 43a4a9b..cf60afd 100644 --- a/internal/installations_test.go +++ b/internal/installations_test.go @@ -22,6 +22,7 @@ func createTestContextForInstallations(flags map[string]interface{}) *cli.Contex // Set default values defaults := map[string]interface{}{ + "client-id": "", "app-id": "", "key": "", "base64-key": "", @@ -91,9 +92,9 @@ func TestInstallations(t *testing.T) { { name: "successful_list_installations_with_key_file", flags: map[string]interface{}{ - "app-id": "123456", - "key": "fixtures/test-private-key.test.pem", - "hostname": "api.github.com", + "client-id": "123456", + "key": "fixtures/test-private-key.test.pem", + "hostname": "api.github.com", }, setupMocks: func() { httpmock.RegisterResponder("GET", "https://api.github.com/app/installations?per_page=100&page=0", @@ -104,7 +105,7 @@ func TestInstallations(t *testing.T) { { name: "successful_list_installations_with_base64_key", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "base64-key": keyBase64, "hostname": "api.github.com", }, @@ -117,9 +118,9 @@ func TestInstallations(t *testing.T) { { name: "successful_list_multiple_installations", flags: map[string]interface{}{ - "app-id": "123456", - "key": "fixtures/test-private-key.test.pem", - "hostname": "api.github.com", + "client-id": "123456", + "key": "fixtures/test-private-key.test.pem", + "hostname": "api.github.com", }, setupMocks: func() { httpmock.RegisterResponder("GET", "https://api.github.com/app/installations?per_page=100&page=0", @@ -130,9 +131,9 @@ func TestInstallations(t *testing.T) { { name: "successful_empty_installations_list", flags: map[string]interface{}{ - "app-id": "123456", - "key": "fixtures/test-private-key.test.pem", - "hostname": "api.github.com", + "client-id": "123456", + "key": "fixtures/test-private-key.test.pem", + "hostname": "api.github.com", }, setupMocks: func() { httpmock.RegisterResponder("GET", "https://api.github.com/app/installations?per_page=100&page=0", @@ -143,9 +144,9 @@ func TestInstallations(t *testing.T) { { name: "successful_with_custom_hostname_without_api_path", flags: map[string]interface{}{ - "app-id": "123456", - "key": "fixtures/test-private-key.test.pem", - "hostname": "github.company.com", + "client-id": "123456", + "key": "fixtures/test-private-key.test.pem", + "hostname": "github.company.com", }, setupMocks: func() { httpmock.RegisterResponder("GET", "https://github.company.com/api/v3/app/installations?per_page=100&page=0", @@ -156,9 +157,9 @@ func TestInstallations(t *testing.T) { { name: "successful_with_custom_hostname_with_api_path", flags: map[string]interface{}{ - "app-id": "123456", - "key": "fixtures/test-private-key.test.pem", - "hostname": "github.company.com/api/v3", + "client-id": "123456", + "key": "fixtures/test-private-key.test.pem", + "hostname": "github.company.com/api/v3", }, setupMocks: func() { httpmock.RegisterResponder("GET", "https://github.company.com/api/v3/app/installations?per_page=100&page=0", @@ -169,9 +170,9 @@ func TestInstallations(t *testing.T) { { name: "successful_with_mixed_case_hostname", flags: map[string]interface{}{ - "app-id": "123456", - "key": "fixtures/test-private-key.test.pem", - "hostname": "GitHub.Company.COM", + "client-id": "123456", + "key": "fixtures/test-private-key.test.pem", + "hostname": "GitHub.Company.COM", }, setupMocks: func() { httpmock.RegisterResponder("GET", "https://github.company.com/api/v3/app/installations?per_page=100&page=0", @@ -182,7 +183,7 @@ func TestInstallations(t *testing.T) { { name: "error_no_key_specified", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", }, setupMocks: func() {}, expectedError: "either --key or --base64-key must be specified", @@ -190,7 +191,7 @@ func TestInstallations(t *testing.T) { { name: "error_both_keys_specified", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "key": "fixtures/test-private-key.test.pem", "base64-key": keyBase64, }, @@ -200,8 +201,8 @@ func TestInstallations(t *testing.T) { { name: "error_invalid_key_file", flags: map[string]interface{}{ - "app-id": "123456", - "key": "fixtures/nonexistent.pem", + "client-id": "123456", + "key": "fixtures/nonexistent.pem", }, setupMocks: func() {}, expectedError: "unable to read key file", @@ -209,7 +210,7 @@ func TestInstallations(t *testing.T) { { name: "error_invalid_base64_key", flags: map[string]interface{}{ - "app-id": "123456", + "client-id": "123456", "base64-key": "invalid-base64-string", }, setupMocks: func() {}, @@ -218,9 +219,9 @@ func TestInstallations(t *testing.T) { { name: "error_http_request_fails", flags: map[string]interface{}{ - "app-id": "123456", - "key": "fixtures/test-private-key.test.pem", - "hostname": "api.github.com", + "client-id": "123456", + "key": "fixtures/test-private-key.test.pem", + "hostname": "api.github.com", }, setupMocks: func() { httpmock.RegisterResponder("GET", "https://api.github.com/app/installations?per_page=100&page=0", @@ -231,9 +232,9 @@ func TestInstallations(t *testing.T) { { name: "error_http_status_not_200", flags: map[string]interface{}{ - "app-id": "123456", - "key": "fixtures/test-private-key.test.pem", - "hostname": "api.github.com", + "client-id": "123456", + "key": "fixtures/test-private-key.test.pem", + "hostname": "api.github.com", }, setupMocks: func() { httpmock.RegisterResponder("GET", "https://api.github.com/app/installations?per_page=100&page=0", @@ -244,9 +245,9 @@ func TestInstallations(t *testing.T) { { name: "error_invalid_json_response", flags: map[string]interface{}{ - "app-id": "123456", - "key": "fixtures/test-private-key.test.pem", - "hostname": "api.github.com", + "client-id": "123456", + "key": "fixtures/test-private-key.test.pem", + "hostname": "api.github.com", }, setupMocks: func() { httpmock.RegisterResponder("GET", "https://api.github.com/app/installations?per_page=100&page=0", diff --git a/internal/key.go b/internal/key.go index 3eb143f..7a68e28 100644 --- a/internal/key.go +++ b/internal/key.go @@ -36,13 +36,13 @@ func readKeyBase64(keyBase64 string) (*rsa.PrivateKey, error) { return key, nil } -func generateJWT(appID string, expiry int, key *rsa.PrivateKey) (string, error) { +func generateJWT(iss string, expiry int, key *rsa.PrivateKey) (string, error) { iat := jwt.NewNumericDate(time.Now().Add(-60 * time.Second)) exp := jwt.NewNumericDate(time.Now().Add(time.Duration(expiry) * 60 * time.Second)) token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ "iat": iat, "exp": exp, - "iss": appID, + "iss": iss, }) signedToken, err := token.SignedString(key) if err != nil { From af6cc87a09b2576f52e20e3daa3a0d5d91a3e389 Mon Sep 17 00:00:00 2001 From: "E-Love (Eric Loveland)" Date: Thu, 16 Jul 2026 15:00:14 -0400 Subject: [PATCH 2/2] Update workflow example to use var for client ID Per GitHub and common OAuth guidance, the client ID does not need to be secret: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/making-authenticated-api-requests-with-a-github-app-in-a-github-actions-workflow#authenticating-with-a-github-app --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 11cc76f..a500fe7 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,8 @@ Successfully revoked installation token ### Example in a workflow +This follow's [GitHub's guide](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/making-authenticated-api-requests-with-a-github-app-in-a-github-actions-workflow) for using a GitHub App in a GitHub Actions workflow. +
Expand to show instructions @@ -248,9 +250,9 @@ Successfully revoked installation token 1. You need to create a secret to store the **applications private key** securely (this can be an organization or a repository secret): ![Create private key secret](images/create_secret.png) -1. You need to create another secret to store the **client ID** ([GitHub recommended](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/making-authenticated-api-requests-with-a-github-app-in-a-github-actions-workflow#authenticating-with-a-github-app)), or app ID security (same as the step above). +1. Store the **client ID** ([GitHub recommended](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/making-authenticated-api-requests-with-a-github-app-in-a-github-actions-workflow#authenticating-with-a-github-app)), or app ID as a [repository or organization variable](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables). -1. The secrets need to be provided as an environment variable then encoded into base64 as show in the workflow example: +1. The private key secret and client ID variable need to be provided as environment variables. The private key is encoded into base64 as shown in the workflow example below. This example is designed to run on GitHub Enterprise Server. To use the same workflow with GitHub.com update the hostname to `api.github.com` and change the API URL in the testing step. @@ -268,7 +270,7 @@ jobs: steps: - name: "Install gh-token" run: gh extension install Link-/gh-token - # Create access token with a GitHub App Client ID and Key + # Create access token with a GitHub App client ID and key # We use the private key stored as a secret and encode it into base64 # before passing it to gh-token - name: "Create access token" @@ -280,7 +282,7 @@ jobs: | jq -r ".token") echo "token=$token" >> $GITHUB_OUTPUT env: - APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }} + APP_CLIENT_ID: ${{ vars.APP_CLIENT_ID }} APP_PRIVATE_KEY: ${{ secrets.APP_KEY }} # To test the token we will use it to fetch the list of repositories # belonging to our organization