Gal Yakir
Gal Yakir

Reputation: 155

Generate a JWT from GitHub App PEM private key in Go

I'm trying to use GitHub App and I need to generate a JWT for authenticating (https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#generating-a-private-key) I'm trying to do that using Goland. How can I generate a JWT from PEM private key in Go??

Upvotes: 2

Views: 1813

Answers (3)

thisiskelvin
thisiskelvin

Reputation: 4202

To successfully create a github app jwt token from a private permissions file in Go, the following jwt claims are required:

  • iat: the "issued at" date of the token (minus 60 seconds for clock float)
  • exp: the expiry date of the token (no more than 10 minutes from the iat
  • iss: the App ID of the Github app (Note: This is not the client id of the app).

Once this is in place, a new jwt token can be created which can be used to communicate with the github api.

package main

import "github.com/golang-jwt/jwt/v5"

func main() {
    pemFilePath := "private-key.pem" // replace

    appId := "<app-id>" // replace
    now := time.Now()

    token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
        "iat": jwt.NewNumericDate(now.Add(-time.Minute)),
        "exp": jwt.NewNumericDate(now.Add(5 * time.Minute)),
        "iss": appID,
    })

    pemKey, _ := ioutil.ReadFile(pemFilePath)

    privateKey, _ := jwt.ParseRSAPrivateKeyFromPEM(pemKey)

    tokenString, err := token.SignedString(privateKey)
    if err != nil {
        panic(err)
    }
    
    fmt.Println(tokenString)
}

In the example above, we use ioutil.ReadFile to read the bytes of the pem file (this is supplied by Github). When then parse the bytes and finally create a signed string with it.

Notes:

  • Example shows exp set to 5 minutes. It can be no longer than 10 minutes from the iat. It would be better to .Add() from an iat value to ensure it is the correct time.
  • now.Add(-time.Minute) will essentially subtract the current time by a minute

Hope this helps.

Upvotes: 2

Alex F
Alex F

Reputation: 3539

I suggest reading code from this repository:

https://github.com/bradleyfalzon/ghinstallation

I don't know why, but the code in the answer from @JesseB above didn't work for me - it always throws: 401 Unauthorized. Although this repository does use golang-jwt package internally

Upvotes: 0

Jesse B
Jesse B

Reputation: 76

The jwt-go library has all the tools you need, and is fairly well documented. You can find it at https://github.com/golang-jwt/jwt.

Assuming you understand what JWTs are and how they're structured, and that you can get that PEM key as a []byte, the process is roughly:

  1. Add "github.com/golang-jwt/jwt/v4" to your imports.
  2. Create a set of claims, which can include the RegisteredClaims type and any custom claims you may need.
  3. Create the token with jwt.NewWithClaims() - you'll need to provide the appropriate signing method. I've primarily used RS256.
  4. Create the JWT string from the token with token.SignedString().

In practice, it will look something like this:

imports "github.com/golang-jwt/jwt/v4"

type MyCustomClaims struct {
    *jwt.RegisteredClaims
    FooClaim int
    BarClaim string
}

func CreateJWT(pemKey []byte) string {
    // expires in 60 minutes
    expiration := time.Now().Add(time.Second * 3600)

    claims := MyCustomClaims{
        RegisteredClaims: &jwt.RegisteredClaims{
            Issuer:    "Example Code Inc.",
            ExpiresAt: jwt.NewNumericDate(expiration),
            Subject:   "JWT Creation",
        },
        FooClaim: 123,
        BarClaim: "bar",
    }

    token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)

    privateKey, _ := jwt.ParseRSAPrivateKeyFromPEM(pemKey)

    myJWT, _ := jwt.SignedString(privateKey)

    return myJWT
}

Upvotes: 6

Related Questions