DEV Community

Thai Pangsakulyanont
Thai Pangsakulyanont

Posted on

Authenticating as a GitHub App in a GitHub Actions workflow

Oftentimes we need to make our GitHub Actions workflow communicate with GitHub APIs. Many authentication methods exist, and each comes with its own pros and cons. You can use the built-in GITHUB_TOKEN secret, a personal access token, or a GitHub App.

The first two methods are pretty well-documented, but soon I faced some limitations when using these two methods.

  • The GITHUB_TOKEN cannot access other private repositories.
  • Using my personal access token means giving access to every other private repositories that I have access to (including those in other organizations).
  • Creating a bot account costs a team seat.

There exists a third method — I can create a GitHub App and grant it access to select repositories, and make the workflow authenticate with the GitHub API as that app. However, I did not see much documentation about it, and I found the setup process to be more complicated. That’s why I set out to write this article.

I will also introduce a tool I created, obtain-github-app-installation-access-token, to make this process more convenient.

An overview of available authentication methods

Method 1: Using the built-in GITHUB_TOKEN secret

Method 2: Using your personal access token

  • Simple to set up. You can get a token at https://github.com/settings/tokens
  • ✅ The token can trigger other workflows.
  • ✅ It can access all your repos you have access to. This is convenient because you can access other repositories without having to do any other extra set up.
  • 😕 It can access all your repos you have access to. You don’t have a fine grained control over what repositories this token can access, because this token represents you. Personally, I do not put my personal access token in any place where access can be shared with others. That means I do not use it with any CI system, because the token can be leaked when I share a repository with another collaborator.
  • 😕 It is bound to a person. The owner of the token leaving the organization can cause your workflow to break.

Method 3: Creating a bot account and using its personal access token

  • ✅ Simple to set up.
  • ✅ The token can trigger other workflows.
  • ✅ You can control which repositories your token has access to by granting your bot access to select repositories.
  • 😕 It costs a team seat. That’s an extra \$4/month for each bot account.
  • 😕 Managing this account requires sharing passwords.

Method 4: Creating a GitHub App and generating tokens from it

Let’s make a GitHub App!

First, go to https://github.com/settings/apps (for personal apps) or https://github.com/organizations/<organizationName>/settings/apps (for organization-owned app) and click the New GitHub App button.

  • For the Homepage URL (a mandatory field), maybe you can put a link to your repository or organization.
  • Uncheck the Active checkbox under the Webhook section. Otherwise, you need to put in a webhook URL.
  • Select the desired repository permissions and click the Create GitHub App button.
  • After creating, note the App ID at the top of the page.

Generate a private key

Scroll all the way down to the bottom and click the Generate a private key button. You will get a PEM file.

Install the app

Scroll back up to the top and click the Install App link in the sidebar and select the organization/account and repositories to grant access to. After installation, note the Installation ID, which you can find in the URL (it should end with installations/<installationId>).

Checkpoint!

By now we should have these information:

  • The App ID.
  • The Installation ID.
  • ✅ The private key.

Let’s get an installation access token

You can write your own script that mints a JWT and exchanges it for an installation access token. You can also use my Node.js script.

GitHub logo dtinth / obtain-github-app-installation-access-token

A simple CLI to obtain a GitHub App Installation Access Token

obtain-github-app-installation-access-token

A simple CLI to obtain a GitHub App Installation Access Token.

npx obtain-github-app-installation-access-token \
  -a <appId> -i <installationId> -k <path/to/private-key.pem&gt

(An installation access token is then printed out to the standard output.)

This source code is compiled using @zeit/ncc into a single .js file which is then published to npm, so it installs and runs fast!

CI usage

In CI, usually you’d want to store your credentials as a token that contains no special characters or whitespaces, and also includes all the information needed. This CLI supports a CI token, a custom format which is generated using btoa(JSON.stringify({ appId, installationId, privateKey })).

Generating a token

Using a web interface

This is the most convenient way. Fill in the form, drop a private key file, and get a token.

https://dtinth.github.io/obtain-github-app-installation-access-token/

Manually

You can generate such token by running the following Node.js script (replacing with the appropriate…

For convenience, I made a web page where you can fill in forms and get a GitHub Actions workflow script. You can access it here:

https://dtinth.github.io/obtain-github-app-installation-access-token/

https://dev-to-uploads.s3.amazonaws.com/i/vgzb86xz26057o9mdvwu.png

This gives you a credentials token. This is not an installation access token that you can use to make API calls. To get a usable token, we need mint a JWT and exchange it for an installation access token. The obtain-github-app-installation-access-token makes this simpler by handling that process for you and now all you need is a credentials token.

You can invoke it manually like this:

$ npx obtain-github-app-installation-access-token ci "eyJhcHBJZCI6IjY2..."
v1.16d59ba2d3be0712c96451773477395767586351

Now you have a usable token:

$ curl https://api.github.com/installation/repositories \
    -H 'Authorization: Bearer v1.16d59ba2d3be0712c96451773477395767586351' \
    -H 'Accept: application/vnd.github.machine-man-preview+json'
{
  "total_count": 1,
  "repository_selection": "selected",
  ...
}

Note that this token expires automatically after 1 hour. That’s why we need to put the script that obtains the installation access token into our CI workflow script.

Set it up on CI

If you use the above web page to generate a credentials token, it will also give you the GitHub Actions workflow script that you can use. Setting it up requires 2 steps:

  1. Adding a secret, GH_APP_CREDENTIALS_TOKEN, to the GitHub repository.
  2. Adding a workflow script, shown below
   - name: Obtain GitHub App Installation Access Token
     id: githubAppAuth
     run: |
       TOKEN="$(npx obtain-github-app-installation-access-token ci ${{ secrets.GH_APP_CREDENTIALS_TOKEN }})"
       echo "::add-mask::$TOKEN"
       echo "::set-output name=token::$TOKEN"
   - name: Use the obtained token
     run: |
       curl -X POST -H 'Content-Type: application/json' \
         -d '{"context":"test","state":"success"}' \
         "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA?access_token=$GITHUB_TOKEN"
     env:
       GITHUB_TOKEN: ${{ steps.githubAppAuth.outputs.token }}

Conclusions

I hope this article shows how using GitHub Apps can add more flexibility to your CI pipeline without compromising security or costing extra money.

Hope you find this useful, and thanks for reading!

Top comments (5)

Collapse
 
novodes profile image
Eyal Gerber

Great post man.
Very informative!
I wanted, however, to comment on what you said regarding Method 2:
Indeed it is not wise to share your personal access token, however, you can still use it safely with github actions as a secret this way: github.community/t/using-github-ac...

Then it makes method 2 only have one potential flaw and that is if I leave the team then the script won't work anymore for the rest of the team because my personal access token (PAT) won't have access anymore. But the fix is just to replace the secret with a new PAT of a current team member (ideally the admin who usually never leaves).

So Method 2 was what I did eventually and seems the more intuitive way with the a relatively very small downside.

Collapse
 
picturedesk profile image
Mario Ammann

Nice post, thank you. It helped us to replace several workarounds with PAT. However, we want to use the obtain token from Method 4 to read npm packages from github npm-registry (npmrc). But it seems, that this token is not working to download npm-packages. Does anyone has a good idea?

@company:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=***

Collapse
 
nastaliss profile image
Stanislas

Hey !
I used your npm module a lot in my github action. But since I mainly use it in actions that do not install npm by default, I decided to create my own action in pure bash, reusing your logic to achieve the same goal. If you want to take a look :)
github.com/Nastaliss/get-github-ap...

Collapse
 
robwafle profile image
Robert Wafle • Edited

A huge downside to #2 is that a PAT only has an API rate limit of 5,000 calls per hour, while a GitHub application can call 15,000 times per hour.

source: docs.github.com/en/developers/apps...

Collapse
 
kingkratos1827 profile image
King Kratos

How can i fit this in ServiceNow?