Personal Note
There’s been a loooonnnnngggg gap in my posting, part of that is life related (bought a house, etc) and part of that is professionally related (I left ShoppingGives in Sept 2021 to be a Principal Engineer at DELL Technologies). Either way, I’m sorry but I’ve switched from C# as my professional language (it’ll still be my #1 personal project language, to Java so that’s why there’s Java snippits rather than C#.
The difference between OAuth App and GitHub Apps
The first thing to talk about is the difference between GitHub Apps and OAuth Apps and it all kind of boils down to this: OAuth Apps are used to access resources on behalf of a user, while GitHub Apps are able to do that as well, but in addition are installed on repositories and can work without a user’s authentication.
The Official Documentation
https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps
I’m sorry I had to subjugate you to that, but I will probably link to pieces of it anyways, I just feel like it does a bad job with the big picture.
The TLDR
There are 3 steps to authenticating:
- Private Key Generation: Generating Private Key
- JWT Generation: JWT Generation
- Access Code Retrieval: Authenticating as an installation
NOTE: The JWT ONLY GIVES ACCESS TO GET THE ACCESS CODE. You cannot use it to perform actions.
Refined Info
1. Private Key Generation
There’s no missing information here, good job GitHub docs.
2. JWT Generation
So for JWT Generation they only give 1 example and it’s in Ruby. So here’s other ways:
Java
For Java I wasn’t able to figure out how to get the private key without a little OpenSSL magic so the first step you need to do is to convert the .PEM that’s downloaded from the private key generation into der format by doing:
openssl pkcs8 -topk8 -inform PEM -outform DER -in **KEY** -out github-private-key.der -nocrypt
Then load it as a private key and use it to sign the jwt:
public String createJWT(String issuer, long ttlMillis) throws Exception {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
Key key = getPrivateKey(**private key location**);
//Let's set the JWT Claims
JwtBuilder builder = Jwts.builder()
.setIssuedAt(now)
.setIssuer(issuer)
.signWith(signatureAlgorithm, key);
//if it has been specified, let's add the expiration
if (ttlMillis > 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
//Builds the JWT and serializes it to a compact, URL-safe string
return builder.compact();
}
public static PrivateKey getPrivateKey(String filename) throws Exception {
File f = new File(filename);
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int) f.length()];
dis.readFully(keyBytes);
dis.close();
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}
Bash
Found this gist: https://gist.github.com/carestad/bed9cb8140d28fe05e67e15f667d98ad
Copied here for posterity. NOTE: YOU NEED TO CHANGE THE VARIABLES IN HERE TO THE APP ID AND LOCATION OF THE PEM Key
#!/usr/bin/env bash
# Generate JWT for Github App
#
# Inspired by implementation by Will Haley at:
# http://willhaley.com/blog/generate-jwt-with-bash/
# From:
# https://stackoverflow.com/questions/46657001/how-do-you-create-an-rs256-jwt-assertion-with-bash-shell-scripting
thisdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
set -o pipefail
# Change these variables:
app_id=1337
app_private_key="$(< $thisdir/app.key)"
# Shared content to use as template
header='{
"alg": "RS256",
"typ": "JWT"
}'
payload_template='{}'
build_payload() {
jq -c \
--arg iat_str "$(date +%s)" \
--arg app_id "${app_id}" \
'
($iat_str | tonumber) as $iat
| .iat = $iat
| .exp = ($iat + 300)
| .iss = ($app_id | tonumber)
' <<< "${payload_template}" | tr -d '\n'
}
b64enc() { openssl enc -base64 -A | tr '+/' '-_' | tr -d '='; }
json() { jq -c . | LC_CTYPE=C tr -d '\n'; }
rs256_sign() { openssl dgst -binary -sha256 -sign <(printf '%s\n' "$1"); }
sign() {
local algo payload sig
algo=${1:-RS256}; algo=${algo^^}
payload=$(build_payload) || return
signed_content="$(json <<<"$header" | b64enc).$(json <<<"$payload" | b64enc)"
sig=$(printf %s "$signed_content" | rs256_sign "$app_private_key" | b64enc)
printf '%s.%s\n' "${signed_content}" "${sig}"
}
sign
3. Access Code Retrieval
Pretty straightforward in the docs.
Prologue
Hopefully this is more succinct and easier to understand than the GitHub docs and saves someone time!