7

I want to create a github app in python, and I'm stuck at the authentication part. Since they don't support python by default, I have to use a third party library. After I generate the JWT token I can successfully authenticate with curl, but not with the library.

I've tried using PyGithub and Github.py and both returned a "Bad credentials" error, so I must have overlooked something.

import jwt
from github import Github
from dotenv import load_dotenv


load_dotenv()
GITHUB_PRIVATE_KEY = os.getenv('GITHUB_PRIVATE_KEY')
GITHUB_APP_IDENTIFIER = os.getenv('GITHUB_APP_IDENTIFIER')
GITHUB_WEBHOOK_SECRET = os.getenv('GITHUB_WEBHOOK_SECRET')

message = {'iat': int(time.time()),
           'exp': int(time.time()) + (10 * 60),
           'iss': GITHUB_APP_IDENTIFIER}

token = jwt.encode(message, GITHUB_PRIVATE_KEY.strip().encode(), 'RS256')

gh = Github(jwt=token.decode())

for repo in gh.get_user().get_repos():
    print(repo.name)

This curl command returns my app's details:

curl -i -H "Authorization: Bearer YOUR_JWT" -H "Accept: application/vnd.github.machine-man-preview+json" https://api.github.com/app

I expect the code to authenticate and print my repos, however i get

Traceback (most recent call last):
  File "C:/python/jeev/testing.py", line 21, in <module>
    for repo in gh.get_user().get_repos():
  File "C:/python/jeev\venv\lib\site-packages\github\PaginatedList.py", line 62, in __iter__
    newElements = self._grow()
  File "C:/python/jeev\venv\lib\site-packages\github\PaginatedList.py", line 74, in _grow
    newElements = self._fetchNextPage()
  File "C:/python/jeev\venv\lib\site-packages\github\PaginatedList.py", line 199, in _fetchNextPage
    headers=self.__headers
  File "C:/python/jeev\venv\lib\site-packages\github\Requester.py", line 276, in requestJsonAndCheck
    return self.__check(*self.requestJson(verb, url, parameters, headers, input, self.__customConnection(url)))
  File "C:/python/jeev\venv\lib\site-packages\github\Requester.py", line 287, in __check
    raise self.__createException(status, responseHeaders, output)
github.GithubException.BadCredentialsException: 401 {'message': 'Bad credentials', 'documentation_url': 'https://developer.github.com/v3'}

Github3.py version:

import jwt
import github3
from dotenv import load_dotenv


load_dotenv()
GITHUB_PRIVATE_KEY = os.getenv('GITHUB_PRIVATE_KEY')
GITHUB_APP_IDENTIFIER = os.getenv('GITHUB_APP_IDENTIFIER')
GITHUB_WEBHOOK_SECRET = os.getenv('GITHUB_WEBHOOK_SECRET')

gh = github3.github.GitHub()
gh.login_as_app(GITHUB_PRIVATE_KEY.encode(), GITHUB_APP_IDENTIFIER)
gh.me()

Same 401 bad credentials exception is raised. I've included a print in the login_as_app function, so now it outputs the JWT token, i used it with the curl command and I get what I want. Weird.

4 Answers 4

7

With PyGithub, you have used the API wrong

This will do it

from github import Github, GithubIntegration

with open("apps-private-key.pem", "r") as secret:
    private_key = secret.read()

GITHUB_APP_ID = "1234"
integration = GithubIntegration(
    GITHUB_APP_ID, private_key, base_url="https://github.com/api/v3")

install = integration.get_installation("owner", "repository")
access = integration.get_access_token(install.id)

# And here it is :)
print(access.token)

gh = Github(login_or_token=access.token,
            base_url="https://github.com/api/v3")

# your operations

Took me a while to figure out this sequence, PyGithub's Docs are missing some bits.

Sign up to request clarification or add additional context in comments.

2 Comments

What does the structure of your private_key variable look like? Does it have the "-----BEGIN PRIVATE KEY-----" / "-----END PRIVATE KEY-----" parts?
Nevermind, adding those onto the beginning and end of the key seemed to work. Thanks!
3

Just to extend on @ilo s answer how to authenticate as an installation:

import github3

key_file = 'private-key.pem'

GITHUB_PRIVATE_KEY = open(key_file, 'r').read()
GITHUB_APP_IDENTIFIER = "6"

gh = github3.github.GitHub()

# Login as app
gh.login_as_app(GITHUB_PRIVATE_KEY.encode(), GITHUB_APP_IDENTIFIER)

# Login to the installation, assuming only a single one
installations = [installation.id for installation in gh.app_installations()]
gh.login_as_app_installation(GITHUB_PRIVATE_KEY.encode(), GITHUB_APP_IDENTIFIER, installations[0])

# Access information
# e.g. the rate limit
print(gh.rate_limit())
# or access token to checkout the repository
print(gh.session.auth.token)

If there are more than one authentication, the installations can be filtered by comparing installation.account['login'] with the organization/user name.

Comments

2

Typical case of RTFM: I should have authenticated as a installation.

Comments

0

Thank you to this answer which helped me to get it working. I think the PyGithub API has moved on a little since then, though, with that method being deprecated, so here's some updated code for authenticating via an org-installed app:

from github import Auth, Github, GithubIntegration
from github.Auth import AppAuth

CLIENT_ID = "xxxxx" # your app's client ID
PRIVATE_KEY_FILENAME = "whatever.pem" # your app's private key file
ORG = "MyOrg" # the organization into which your app has been installed

with open(PRIVATE_KEY_FILENAME, "r") as f:
    private_key = f.read()

app_auth = AppAuth(CLIENT_ID, private_key)
integration = GithubIntegration(auth=app_auth)
install = integration.get_org_installation(ORG)
access = integration.get_access_token(install.id)

auth = Auth.Token(access.token)
gh = Github(auth=auth)

And I don't know how I didn't see this before, but the PyGithub docs have examples for this exact use case too, e.g. App installation authentication.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.