GitHubApps and iOS

Hani Ibrahim
4 min readJan 31, 2021

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:
  1. > Go to Settings, Developer settings, GitHub Apps
  2. > Tap on Edit for the GitHubApp that you created
  3. > Go to Install App
  4. > Tap on Install that is beside your account or organization
  • Get the App ID:
  1. > Go to Settings, Developer settings, GitHub Apps
  2. > Tap on Edit for our GitHubApp
  3. > Go to General (you should be already on it)
  4. > You will find App ID in the About section. Save if for now :)
  • Get the Installation ID:
  1. > Go to Settings, Applications
  2. > Tap on Configure
  3. > 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
  4. > In that sample URL the installation ID is 123456789. Save if for now too :)
  • Generate Private Key:
  1. > Go to Settings, Developer settings, GitHub Apps
  2. > Tap on Edit for our GitHubApp
  3. > Go to General (you should be already on it)
  4. > Scroll down to the Private keys section and then tap on Generate a private key
  5. > 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:

  1. .pem file
  2. App ID
  3. 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.

--

--