Github App JSON Web Token in Phoenix

elixir
phoenix
github
Published

November 9, 2022

I recently started my very first Elixir and Phoenix project, mostly because LiveView seems very promising. LiveView takes advantage of Elixir’s actor model, and should make it easy to create interactive websites, without needing any JavaScript. I am still unsure if it delivers on that promise, but I have high hopes.

The application I am building is tightly integrated with GitHub, and to use the GitHub App API, you need to generate a JSON Web Token. Here is how to do that:

Add Joken as a dependency in mix.exs, and install it by running mix deps.get afterwards:

mix.exs
defp deps do
  [
    …,
    {:joken, "~> 2.5.0"}
  ]
end

Generate a private key for your GitHub App and download it. Configure a Joken signer:

config/dev.exs
config :joken,
  github: [
    signer_alg: "RS256",
    # Insert the contents from your private key her:
    key_pem: """
    -----BEGIN RSA PRIVATE KEY-----
    ...
    -----END RSA PRIVATE KEY-----
    """
  ]

You should probably not commit the private key to Git. For one approach on how to store secrets without commiting them, consult Handling Environment Variables in Phoenix.

Next add a module that will be used to generate the JWT:

lib/your_app/github_app_token.ex
defmodule YourApp.GitHubAppToken do
  use Joken.Config, default_signer: :github

  def token_config do
    %{}
    
    # issued at time, 60 seconds in the past to allow for clock drift
    |> add_claim("iat", fn -> (DateTime.utc_now() |> DateTime.to_unix()) - 60 end)
    
    # JWT expiration time (10 minute maximum)
    |> add_claim("exp", fn -> (DateTime.utc_now() |> DateTime.to_unix()) + 10 * 60 end)
    
    # GitHub App's identifier
    |> add_claim("iss", fn -> "YOUR_APP_ID" end)
  end
end

Now you are ready to create a token and perform a request to GitHub’s API, here using HTTPoison:

jwt = YourApp.GitHubAppToken.generate_and_sign!()

headers = [
  {"Authorization", "Bearer #{jwt}"},
  {"Accept", "application/vnd.github+json"}
]

HTTPoison.get("https://api.github.com/app/installations", headers)

That’s it.