GitHubApps and iOS
Authentication is an important part for any CI environment. After building the app and configuring Fastlane scripts, now we want to run the scripts periodically and to do that we need a mac machine and a GitHub account to login with it on that machine. You could use your personal GitHub account, but that’s simply not secure. Another option is creating a bot user but your company’s policy could disallow that for security reasons. Luckily GitHub has another way to authenticate, called GitHubApps.
Setting up GitHubApp
To setup GitHubApp, follow these steps:
- Create a GitHubApp using this guide. Make sure to provide the needed permissions.
- Install the app on your account or organization:
- > Go to
Settings
,Developer settings
,GitHub Apps
- > Tap on
Edit
for the GitHubApp that you created - > Go to
Install App
- > Tap on
Install
that is beside your account or organization
- Get the App ID:
- > Go to
Settings
,Developer settings
,GitHub Apps
- > Tap on
Edit
for our GitHubApp - > Go to
General
(you should be already on it) - > You will find
App ID
in theAbout
section. Save if for now :)
- Get the Installation ID:
- > Go to
Settings
,Applications
- > Tap on
Configure
- > Now look at the URL of that page and you will find that it looks like
https://github.com/settings/installations/123456789
. The installation ID is the number at the end of this URL - > In that sample URL the installation ID is
123456789
. Save if for now too :)
- Generate Private Key:
- > Go to
Settings
,Developer settings
,GitHub Apps
- > Tap on
Edit
for our GitHubApp - > Go to
General
(you should be already on it) - > Scroll down to the
Private keys
section and then tap onGenerate a private key
- > This would download a
.pem
file. Note: You need to store this.pem
file on your mac machine and don't store it in your repo for security reasons.
Generating AccessToken
From the previous section, You should have the following:
- .pem file
- App ID
- Installation ID
Now configure the below script with these values to generate access token
require 'openssl'
require 'jwt'
require 'net/http'# Configuration
private_pem_path = # Replace with the pem file path
app_id = # Replace with your "App ID"
installation_id = # Replace with your "Installation ID"# Generate JWT
private_pem = File.read(private_pem_path)
payload = {
iat: Time.now.to_i,
exp: Time.now.to_i + (10 * 60),
iss: app_id
}
private_key = OpenSSL::PKey::RSA.new(private_pem)
jwt = JWT.encode(payload, private_key, "RS256")# Generate AccessToken
access_token_url = URI.parse("https://api.github.com/app/installations/#{installation_id}/access_tokens")
access_token_request = Net::HTTP::Post.new(access_token_url) ndskl svnsdkl s
access_token_request["Authorization"] = "Bearer #{jwt}"
access_token_request["Accept"] = "application/vnd.github.v3+json"
request_options = {
use_ssl: access_token_url.scheme == "https",
}
response = Net::HTTP.start(access_token_url.hostname, access_token_url.port, request_options) do |http|
http.request(access_token_request)
end
json_response = JSON.parse(response.body)
access_token = json_response["token"]puts "access_token: #{access_token}"
The generated AccessToken has a lifespan of 10 minutes only. So you will probably need to generate a new one whenever you want to do any action related with GitHub.
Fastlane GitHub Actions and GitHubApp
GitHub Actions like pushing new tag or release or create a pull request are essential parts of any Fastlane script for any app. Fastlane has a built-in functions for these actions like push new tags fastlane push_git_tags tag:TAG_NUMBER
. Unfortunately at the time of integrating with GitHubApp into our iOS app, Fastlane Actions was only supporting Basic Authentication
while GitHubApp needs Bearer Authentication
.
For that reason we had to use low level GitHub api provided by Fastlane github_api
, so that we inject our token the way we want.
github_api(
server_url: 'https://api.github.com',
api_token: '',
headers: {
'Authorization' => "Bearer #{access_token}",
'Accept' => 'application/vnd.github.v3+json'
},
http_method: 'POST',
path: "repos/#{repo_name}/git/refs",
body: {
'ref': "refs/tags/#{tag}",
'sha': tag_commit_hash
}
)
Similarly, we could change the request to create a release instead of tag as below
github_api(
server_url: 'https://api.github.com',
api_token: '',
headers: {
'Authorization' => "Bearer #{access_token}",
'Accept' => 'application/vnd.github.v3+json'
},
http_method: 'POST',
path: "repos/#{repo_name}/releases",
body: {
'name': name,
'tag_name': tag_name,
'body': body,
'target_commitish': tag_commit_hash,
'draft': false,
'prerelease': false
}
)
Also we could create a pull request in the same way
github_api(
server_url: 'https://api.github.com',
api_token: '',
headers: {
'Authorization' => "Bearer #{access_token}",
'Accept' => 'application/vnd.github.v3+json'
},
http_method: 'POST',
path: "repos/#{repo_name}/pulls",
body: {
'title': title,
'head': head,
'base': base,
'body': body,
'maintainer_can_modify': true,
'draft': false
}
)
Fastlane Match and GitHubApp
At the time when we were integrating with GitHubApp. Fastlane match was only sending the request with Basic Authentication
and GitHubApp needs Bearer Authentication
. We couldn't find a way to inject the AccessToken in the header as we did in GitHub Actions. However luckily GitHub support injecting the AccessToken in the repo URL. So instead of supplying the URL as
"https://github.com/#{repo_name}.git"
We could inject the token in the url this way, and supply that url to git_url
property in the Fastlane match function.
"https://x-access-token:#{access_token}@github.com/#{repo_name}.git"
Danger.systems and GitHubApp
In my team we are using Danger.systems to post a comment on a PR with the status of various aspects as for example running Swiftlint and report its status in the PR.
To make Danger works with GitHub app, we had to go with the same strategy we did with Fastlane match and inject the AccessToken in the URL
ENV["DANGER_GITHUB_API_TOKEN"] = access_token
ENV["DANGER_GITHUB_HOST"] = "x-access-token:#{access_token}@github.com"
However with Danger we had to do an extra step as there were some commands that Danger was using the .ssh tokens and not the provided AccessToken with them. So we fixed that by overriding the URL of the repo itself to inject the AccessToken as well.
system("git remote set-url origin https://x-access-token:#{access_token}@github.com/#{repo_name}.git")
Final thoughts
GitHubApps is a great tool to authenticate with GitHub without using your own credentials. However, at the time when I was experimenting with GitHubApps, many tools didn’t support Bearer Authentication
. Now, while writing this article, I found that Fastlane added the support for Bearer Authentication
in GitHub Actions.