There's a couple of steps needed for this: firstly you need to get some information from GitHub by hand, and then there is a little dance that your app needs to do to swap its authentication secrets for a temporary code that can be used to authenticate a git clone.
Gather information

Before you can write a function to do this, you need three pieces of information, all of which are available from the App's settings. To get there
- Go to the organization you have created the App for
- Go to
Settings > Developer Settings > GitHub Apps - Click
Editnext to the name of the App you're using, and authenticate with 2FA
The three pieces of information you need are:
The App ID

This is in the General page, in the About section at the top.
The Installation ID

If you haven't already, you also need to install the App into the Organization. Once this is done, go back to the Install App page in the App settings, and copy the link for the installation settings. Paste it into your editor and get the number from the end. The link should have the form https://github.com/apps/{app_name}/installations/{installation_id}; the part after the last / is the installation ID.
(If you have multiple installations of your app, there may be a way to get this programmatically; I haven't looked into this as I didn't need it for my use case.)
PEM file

This is how you prove to GitHub that you are in control of the App. Go back to the General page in the App settings, and scroll down to the Private keys section. Click the Generate a private key button; this will immediately generate a .pem file and download it to your machine.
Do not commit this to your repository unless you want everyone who can see the repository to be able to authenticate to GitHub as you.
The code
Once you have these three things, the steps you need in code are:
- Load your PEM
- Use the PEM to create a JSON Web Token that will authenticate your API call
- Call the GitHub API to get an installation token
- (Use the installation token to clone the repository of interest.)
Get the installation token
Code to do the first three steps could look like this:
from datetime import datetime
import jwt
import requests
def get_installation_access_token(
pem_filename: str, app_id: str, installation_id: str
) -> str:
"""
Obtain and return a GitHub installation access token.
Arguments:
pem_filename: Filename of a PEM file generated by GitHub to
authenticate as the installed app.
app_id: The application ID
installation_id: The ID of the app installation.
Returns:
The installation access token obtained from GitHub.
"""
# With thanks to https://github.com/orgs/community/discussions/48186
now = int(datetime.now().timestamp())
with open(pem_filename, "rb") as pem_file:
signing_key = jwt.jwk_from_pem(pem_file.read())
payload = {"iat": now, "exp": now + 600, "iss": app_id}
jwt_instance = jwt.JWT()
encoded_jwt = jwt_instance.encode(payload, signing_key, alg="RS256")
response = requests.post(
"https://api.github.com/app/installations/" f"{installation_id}/access_tokens",
headers={
"Authorization": f"Bearer {encoded_jwt}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
)
if not 200 <= response.status_code < 300:
raise RuntimeError(
"Unable to get token. Status code was "
f"{response.status_code}, body was {response.text}."
)
return response.json()["token"]
Pass in the information collected above as the three parameters to the function. Note that this depends on the jwt and requests packages, both available under those names from pip.
This will give an installation token that is valid for an hour. (This is much less time than the PEM file is valid, because it has a lot less security. That's the reason this dance is needed—you're trading something pretty secure for something that is less secure but easier to use with git clone; because it's less secure, it has to be time limited instead to reduce the chance of it getting stolen.)
Clone the repository
Assuming that you have a repository URL in the form
repo_url = https://github.com/organization/repository_name
then you can clone the repository as:
import git
if not original_url.startswith("https://"):
raise ValueError("Need an HTTPS URL")
auth_url = f"https://x-access-token:{token}@{original_url[8:]}"
git.Repo.clone_from(
auth_url,
deployment["tempdir_path"] / "repo",
branch="deployment",
)
Here I've used the GitPython library for Python. Equivalently, you could use the shell command
$ git clone https://x-access-token:${TOKEN}@github.com/organization/repository_name
where ${TOKEN} contains the result of calling the above Python function.
Credits
Many thanks to loujr on the GitHub Community for the guide that eventually clued me into how to do this. I've stripped out the need to use command-line arguments and to manually pass the JWT into curl, instead keeping everything in Python.
python - How do I get an GitHub app installation token to authenticate cloning a repository - Stack Overflow
oauth 2.0 - how to use github api token in python for requesting - Stack Overflow
python - gitpython: How to authenticate as a Github app? - Stack Overflow
PyCharm + GitHub issues
Videos
Here's some code that might help you out.
Examples:
Example 1 (auth):
username = 'user'
token = 'token'
login = requests.get('https://api.github.com/search/repositories?q=github+api', auth=(username,token))
Example 2 (headers):
headers = {'Authorization': 'token ' + token}
login = requests.get('https://api.github.com/user', headers=headers)
print(login.json())
Example 3 (delete repo):
user = 'username'
repo = 'some_repo' # Delete this repo
headers = {'Authorization': 'token ' + token}
login = requests.delete('https://api.github.com/' + 'repos/' + user + '/' + repo, headers=headers)
Example 4 (create repo):
repo = 'some_repo'
description = 'Created with api'
payload = {'name': repo, 'description': description, 'auto_init': 'true'}
login = requests.post('https://api.github.com/' + 'user/repos', auth=(user,token), data=json.dumps(payload))
You might want to take a look at the following docs:
Requests Docs
Github API docs
I hope this helps.
For one, I would recommend using a wrapper for the API. You're asking a lot of questions on here that could be simplified by finding a wrapper whose API you appreciate. There's a list of wrappers written in Python here.
As for your actually answering your question, the GitHub documentation is fairly clear that you need to send the Authorization header. Your call would actually look like this:
self.headers = {'Authorization': 'token %s' % self.api_token}
r = requests.post(url, headers=self.headers)
Since it seems like you're using requests and a class, might I be so bold as to make a recommendation? Let's say you're doing something like making a client for the API. You might have a class like so:
class GitHub(object):
def __init__(self, **config_options):
self.__dict__.update(**config_options)
self.session = requests.Session()
if hasattr(self, 'api_token'):
self.session.headers['Authorization'] = 'token %s' % self.api_token
elif hasattr(self, 'username') and hasattr(self, 'password'):
self.session.auth = (self.username, self.password)
def call_to_the_api(self, *args):
# do stuff with args
return self.session.post(url)
The Session object will take care of the authentication for you (either by the tokens or username and password combination).
Also, if you end up deciding to use github3.py for your API wrapper needs, there's a tag on here for it.