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.
To authenticate a user, a client application must send a JSON Web Token (JWT) in the authorization header of the HTTP request to your backend API. API Gateway validates the token on behalf of your API, so you don't have to add any code in your API to process the authentication.
If you want to do real logout you must go with OAuth2. Authentication with JWT token can not logout actually. Because you don't have an Authentication Server that keeps track of tokens. If you want to provide an API to 3rd party clients, you must use OAuth2 also.
Both API key and JWT are used for authentication and authorization, but they do it differently. Authentication allows the user or application to use one or more methods of the API. Authorization defines how they can use those methods.
Enter the downloaded private key in the Private Key field of the Verify Signature section. Ensure that you have removed the public certificate from the Public Key or Certificate field , if the certificate exists. A token is generated in the Encoded section. Copy this token on your system for further use.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With