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
- ✅ No set-up required. It just works, even for forked repositories.
- ✅ The token can access only the repo containing the workflow file. The token cannot be used to access other private repositories.
- 😕 The token can access only the repo containing the workflow file. If you need to access other private repos (or need a write access to other public repos), this token won’t work.
- 😕 The token cannot trigger other workflows. If you have a workflow that creates a release and another workflow that gets run when someone creates a release, then the first workflow won’t trigger the second workflow if it uses this token.
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
- ✅ You can control which repositories your token has access to by installing the GitHub App to select repositories.
- ✅ An organization can own many GitHub Apps, and they don’t cost a team seat.
- ✅ GitHub Apps has a more fine-grained permission model.
- ✅ The token can trigger other workflows.
- 😕 Not officially documented. The GitHub Actions’ official docs only mentions using a personal access token but not GitHub Apps. It works nonetheless.
- 😕 The set-up is a bit more complicated. You need to register a new GitHub App, install it, generate a private key, and write some extra code in your CI pipeline to mint a JWT from that private key and finally exchange that JWT for an ephemeral installation access token that your workflow can use with normal GitHub APIs.
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.
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>
(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/
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:
- Adding a secret,
GH_APP_CREDENTIALS_TOKEN
, to the GitHub repository. - 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' </span>
-d '{"context":"test","state":"success"}' </span>
"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)
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.
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...
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=***
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...
How can i fit this in ServiceNow?