2

Authenticating with Github APIs as a Github App

 1 year ago
source link: https://aviralrma.medium.com/authenticating-with-github-apis-as-a-github-app-e368d1343654
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Authenticating with Github APIs as a Github App

Recently, I got a chance to integrate with Github APIs. I had created a github app to interact with a repo: create and update files there mostly. However, the integration was not very straightforward, mostly from the authentication standpoint and I struggled to find my way around it, so of course my instinct was to write about it. While this blog post is not about that use-case, let me just touch on it as well:

We were using a GitHub repo as a ledger to maintain custom Jupyter kernel specs and then using git-syncer to sync these files to a shared volume in JEG pod where they were picked up by enterprise gateway service. Apart from the obvious benefits of separation of concerns, clean auditing, we also got checks (pre-commit) & triggers (webhook) by using GitHub.

Now back to the topic, Github app installation authentication is a 2-step process, which you can read about here, but it’s mostly first generating a signed JWT and then using that JWT to make a call to get installation token. The JWT token expires every 10 mins (max) and the installation token expired every 1 hour (max). The authentication flow was complex in itself even without the refresh flow.

Being the Java developer that I am, I quickly looked for any readymade solutions. With a little research I got onto this: https://github-api.kohsuke.org/, the API design looked good even for file operations. They even had a handy authentication document. This is how everything looked post the integration:

  public static PrivateKey get(String filename) throws Exception {
byte[] keyBytes = Files.readAllBytes(Path.of(filename));
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}

public static String createJWT(String githubAppId, long ttlMillis, String pemFilePath) throws Exception {
//The JWT signature algorithm we will be using to sign the token
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RS256;

long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);

//We will sign our JWT with our private key
Key signingKey = get(pemFilePath);

//Let's set the JWT Claims
JwtBuilder builder = Jwts.builder()
.setIssuedAt(now)
.setIssuer(githubAppId)
.signWith(signingKey, signatureAlgorithm);

//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();
}

@Bean(name = "github")
public GitHub getGithubClient() {
try {
String jwtToken = AuthUtils.createJWT(githubConfig.getAppId(), 600000, githubConfig.getPemFilePath());
GitHub gitHubApp = new GitHubBuilder()
.withEndpoint(githubConfig.getUri())
.withJwtToken(jwtToken).build();
GHAppInstallation appInstallation = gitHubApp
.getApp()
.getInstallationById(githubConfig.getInstallationId());
Map<String, GHPermissionType> permissions = new HashMap<>();
permissions.put("contents", GHPermissionType.WRITE);
GHAppInstallationToken appInstallationToken = appInstallation.createToken(permissions)
.create();
appInstallationToken.getToken();
return new GitHubBuilder()
.withEndpoint(githubConfig.getUri())
.withAppInstallationToken(appInstallationToken.getToken())
.build();
} catch (Exception ex) {
throw new RuntimeException("Authorization with Github failed", ex);
}
}

Well, this worked. And this what the document suggested as well. As you’d realize this is complex and messy, but even more it doesn’t actually address the refresh logic. And sadly there was no document around it as well.

After a little digging around, I realized the library devs had actually implemented it, but just didn’t have it documented, and it all boiled down to:

@Bean(name = "github")
public GitHub getGithubClient() {
try {
OrgAppInstallationAuthorizationProvider provider = new OrgAppInstallationAuthorizationProvider("orgName",
new JWTTokenProvider(githubConfig.getAppId(),
getPrivateKeyFromFile(githubConfig.getPemFilePath())));
return new GitHubBuilder()
.withEndpoint(githubConfig.getUri())
.withAuthorizationProvider(provider)
.build();
} catch (Exception ex) {
throw new RuntimeException("Authorization with Github failed", ex);
}
}

This was much cleaner, and more importantly fully working.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK