Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bad Credentials, token works fine with curl #1753

Closed
finnkauski opened this issue Nov 12, 2020 · 39 comments · Fixed by #2739
Closed

Bad Credentials, token works fine with curl #1753

finnkauski opened this issue Nov 12, 2020 · 39 comments · Fixed by #2739

Comments

@finnkauski
Copy link

Hey folks,

Used a personal access token as authentication, yet it does not work through python. No issues when using same token with curl, however here is the issue when using it in python.

>>> gh = Github(token)
>>> gh.get_user("finnkauski")
GET https://api.github.com/users/finnkauski {'Authorization': 'token (oauth token removed)', 'User-Agent': 'PyGithub/Python'} None ==> 401 {'date': 'Thu, 12 Nov 2020 12:49:57 GMT', 'content-type': 'application/json; charset=utf-8', 'content-length': '80', 'server': 'GitHub.com', 'status': '401 Unauthorized', 'x-github-media-type': 'github.v3; format=json', 'x-ratelimit-limit': '60', 'x-ratelimit-remaining': '45', 'x-ratelimit-reset': '1605187930', 'x-ratelimit-used': '15', 'access-control-expose-headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset', 'access-control-allow-origin': '*', 'strict-transport-security': 'max-age=31536000; includeSubdomains; preload', 'x-frame-options': 'deny', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1; mode=block', 'referrer-policy': 'origin-when-cross-origin, strict-origin-when-cross-origin', 'content-security-policy': "default-src 'none'", 'vary': 'Accept-Encoding, Accept, X-Requested-With', 'x-github-request-id': 'E402:5A85:2910526:2E6F2D3:5FAD2F75'} {"message":"Bad credentials","documentation_url":"https://docs.github.com/rest"}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/arturas/projects/gh_wrangler/.venv/lib/python3.8/site-packages/github/MainClass.py", line 271, in get_user
    headers, data = self.__requester.requestJsonAndCheck(
  File "/Users/arturas/projects/gh_wrangler/.venv/lib/python3.8/site-packages/github/Requester.py", line 317, in requestJsonAndCheck
    return self.__check(
  File "/Users/arturas/projects/gh_wrangler/.venv/lib/python3.8/site-packages/github/Requester.py", line 342, in __check
    raise self.__createException(status, responseHeaders, output)
github.GithubException.BadCredentialsException: 401 {"message": "Bad credentials", "documentation_url": "https://docs.github.com/rest"}
>>> 

Meanwhile in curl:

>>> curl -H "Authorization: token <token>" https://api.github.com/users/finnkauski
{
  "login": "finnkauski",
  "id": 15175738,
  ... 
}

I honestly have no clue what's up. I went into the source and found the Requester code and it looks like it generates the header correctly. But for some reason it doesn't really fly.

I am using version: 1.53

@finnkauski finnkauski changed the title Bad Credentials, completely clueless on what is going on Bad Credentials, token works fine with curl Nov 12, 2020
@finnkauski
Copy link
Author

finnkauski commented Nov 12, 2020

Reinstalled my virtual environment, reinstalled all the packages:

>>> from github import Github
>>> token = "thisismytokenfromdevelopersettingsongithub"
>>> gh = Github(token)
>>> gh.get_user("finnkauski")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/arturas/projects/gh_wrangler/.venv/lib/python3.8/site-packages/github/MainClass.py", line 271, in get_user
    headers, data = self.__requester.requestJsonAndCheck(
  File "/Users/arturas/projects/gh_wrangler/.venv/lib/python3.8/site-packages/github/Requester.py", line 317, in requestJsonAndCheck
    return self.__check(
  File "/Users/arturas/projects/gh_wrangler/.venv/lib/python3.8/site-packages/github/Requester.py", line 342, in __check
    raise self.__createException(status, responseHeaders, output)
github.GithubException.BadCredentialsException: 401 {"message": "Bad credentials", "documentation_url": "https://docs.github.com/rest"}
>>>

Edit
Also just restarted the machine, no luck (obviously last hope here).
Cloned my development repo on my personal machine running Ubuntu, no issues, works first try.

@s-t-e-v-e-n-k
Copy link
Collaborator

s-t-e-v-e-n-k commented Nov 13, 2020

>>> import github
>>> g = github.Github("mytoken")
>>> github.enable_console_debug_logging()
>>> g.get_repo("PyGithub/PyGithub")
GET https://api.github.com/repos/PyGithub/PyGithub {'Authorization': 'token (oauth token removed)', 'User-Agent': 'PyGithub/Python'} None ==> 200 {'date': 'Fri, 13 Nov 2020 06:54:18 GMT' ...

What do you see being sent when you enable debug logging?

@dhruvmanila
Copy link
Contributor

Also, take a look at the scope of the token. Make sure read:user is checked.

@maikelpoot
Copy link

Got the same problem using an Access token with everything checked.

  • OSX 10.14,
  • python3.9, (also tried 3.8 and 3.7 from brew)
  • PyGithub 1.54.1 (Tried downgrading version until 1.43, but non worked. Also py2 did not work)

Also script and token works on a centos7 (py3.6) machine, so it seems to be an os related issue

$ python3 test.py
GET https://api.github.com/repos/PyGithub/PyGithub {'Authorization': 'token (oauth token removed)', 'User-Agent': 'PyGithub/Python'} None ==> 401 {'server': 'GitHub.com', 'date': 'Wed, 14 Apr 2021 07:22:42 GMT', 'content-type': 'application/json; charset=utf-8', 'content-length': '80', 'x-github-media-type': 'github.v3; format=json', 'x-ratelimit-limit': '60', 'x-ratelimit-remaining': '44', 'x-ratelimit-reset': '1618387316', 'x-ratelimit-used': '16', 'access-control-expose-headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset', 'access-control-allow-origin': '*', 'strict-transport-security': 'max-age=31536000; includeSubdomains; preload', 'x-frame-options': 'deny', 'x-content-type-options': 'nosniff', 'x-xss-protection': '0', 'referrer-policy': 'origin-when-cross-origin, strict-origin-when-cross-origin', 'content-security-policy': "default-src 'none'", 'vary': 'Accept-Encoding, Accept, X-Requested-With', 'x-github-request-id': 'DFDB:03C3:23A1503:246AC37:60769842'} {"message":"Bad credentials","documentation_url":"https://docs.github.com/rest"}
Traceback (most recent call last):
  File "/test.py", line 10, in <module>
    repo = g.get_repo("PyGithub/PyGithub")
  File "/usr/local/lib/python3.9/site-packages/github/MainClass.py", line 345, in get_repo
    headers, data = self.__requester.requestJsonAndCheck(
  File "/usr/local/lib/python3.9/site-packages/github/Requester.py", line 315, in requestJsonAndCheck
    return self.__check(
  File "/usr/local/lib/python3.9/site-packages/github/Requester.py", line 340, in __check
    raise self.__createException(status, responseHeaders, output)
github.GithubException.BadCredentialsException: 401 {"message": "Bad credentials", "documentation_url": "https://docs.github.com/rest"}

Curl works fine:

$ curl -s -H "Authorization: token ghp_*******" https://api.github.com/repos/PyGithub/PyGithub | jq .full_name
"PyGithub/PyGithub"

Pip output:

$ pip3 install --upgrade PyGithub
Requirement already satisfied: PyGithub in /usr/local/lib/python3.9/site-packages (1.54.1)
Requirement already satisfied: deprecated in /usr/local/lib/python3.9/site-packages (from PyGithub) (1.2.12)
Requirement already satisfied: requests>=2.14.0 in /usr/local/lib/python3.9/site-packages (from PyGithub) (2.24.0)
Requirement already satisfied: pyjwt<2.0 in /usr/local/lib/python3.9/site-packages (from PyGithub) (1.7.1)
Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.9/site-packages (from requests>=2.14.0->PyGithub) (1.25.11)
Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.9/site-packages (from requests>=2.14.0->PyGithub) (2.10)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.9/site-packages (from requests>=2.14.0->PyGithub) (2020.12.5)
Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.9/site-packages (from requests>=2.14.0->PyGithub) (3.0.4)
Requirement already satisfied: wrapt<2,>=1.10 in /usr/local/lib/python3.9/site-packages (from deprecated->PyGithub) (1.12.1)

@s-t-e-v-e-n-k
Copy link
Collaborator

@maikelpoot And how are you passing the token to the Github() object?

@maikelpoot
Copy link

Tried both g = github.Github(login_or_token="ghp_") as g = github.Github("ghp_") but makes no difference.

Also took a dive in to Requester.py to add some debug prints, but the headers given to cnx.request in __requestRaw seem to be fine

@s-t-e-v-e-n-k
Copy link
Collaborator

Add user_agent to your constructor and see if changing that helps any? Although why that would, I have no idea ...

@maikelpoot
Copy link

Had tried that before, makes no difference but the user-agent in the debug logging :-)

GET https://api.github.com/repos/PyGithub/PyGithub {'Authorization': 'token (oauth token removed)', 'User-Agent': 'myscript'} None ==> 401 {'s

@dmsimard
Copy link

dmsimard commented Jun 4, 2021

I'm also seeing what feels like bogus Bad Credentials errors, for example:

DEBUG:urllib3.connectionpool:https://api.github.com:443 "GET /repos/ansible-collections/community.general/issues/2489 HTTP/1.1" 401 80
2021-06-04 12:47:32,638 DEBUG 696800 connectionpool.py:_make_request:433 https://api.github.com:443 "GET /repos/ansible-collections/community.general/issues/2489 HTTP/1.1" 401 80
ERROR:root:401 {"message": "Bad credentials", "documentation_url": "https://docs.github.com/rest"}
2021-06-04 12:47:32,639 ERROR 696800 github.py:inner:164 401 {"message": "Bad credentials", "documentation_url": "https://docs.github.com/rest"}
ERROR:root:Uncaught exception
Traceback (most recent call last):
  File "/home/dmsimard/dev/git/ansible-community/collection_bot/ansibullbot/decorators/github.py", line 134, in inner
    x = fn(*args, **kwargs)
  File "/home/dmsimard/dev/git/ansible-community/collection_bot/ansibullbot/wrappers/ghapiwrapper.py", line 201, in get_issue
    if issue.update():
  File "/home/dmsimard/dev/virtualenvs/ansibullbot/lib64/python3.9/site-packages/github/GithubObject.py", line 331, in update
    headers, data = self._requester._Requester__check(
  File "/home/dmsimard/dev/virtualenvs/ansibullbot/lib64/python3.9/site-packages/github/Requester.py", line 378, in __check
    raise self.__createException(status, responseHeaders, output)
github.GithubException.BadCredentialsException: 401 {"message": "Bad credentials", "documentation_url": "https://docs.github.com/rest"}

Whereas the same query with the same token works with curl:
curl -s -H "Authorization: token ghp_*****" https://api.github.com/repos/ansible-collections/community.general/issues/2489 | jq

What's weird is that, to my knowledge, it used to work until I deleted the token and created a new one.

At first I thought it could be related to GitHub's new token format but when I tried another "old" token I still had, it also failed with Bad Credentials so I figure the issue might be elsewhere.

@s-t-e-v-e-n-k
Copy link
Collaborator

So I wonder if this is due to redirects -- could you try with 1265747 applied (you can ignore the test) and see if that helps any?

@dmsimard
Copy link

dmsimard commented Jun 9, 2021

I came back to the project I was working on and it suddenly worked again without (to my knowledge) changing anything of significance -- I tried creating a new token and now I can reproduce the "Bad Credentials" issue again.

I deleted the previous token that worked but in hindsight I shouldn't have done that to do more troubleshooting. It's a token I had just created last week. Could there be some sort of propagation period or lag before the token starts working everywhere ?

I will investigate some more and find out if I can get to the bottom of the issue.

Edit: btw applying 1265747 doesn't resolve the issue

@dmsimard
Copy link

I spent some more time on this and it may turn out that the issue was caused by a caching behavior in the tool that I am running where it may cache prior tokens. I'll report back if I find out that the issue is related to pygithub but it doesn't look to be the case. Sorry for the noise !

@mlexs
Copy link

mlexs commented Sep 4, 2021

Running into same issue here. Using PyGithub 1.55

The issue seems to be intermittent - so it would work one time, but won't work next run a few seconds later, then it won't work for X times, and then it might work eventually. Very unreliable = no use.

@scorchio
Copy link

scorchio commented Oct 26, 2021

Something is not working well here. I've been trying to use PyGithub with a new token, it has every permission it needs, works properly for the same query in curl, yet it fails with PyGithub.

edit: After some debugging into the library, it seems that the __authorizationHeader originally set and the actually sent request's Authorization headers are different. The first uses a token <token> value with the token value I've supplied, but the second one uses a Basic auth with a value I haven't recognized after decoding. But after reading this answer on SO, I realized that the same header dropping might be happening here, and indeed, the token is what I had specified in my .netrc file. Once I've updated that, things started to work.

@stale
Copy link

stale bot commented Jan 9, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Jan 9, 2022
@totycro
Copy link

totycro commented Feb 7, 2022

I am having similar issues, thanks to the comment by @scorchio I was able to figure it out: My code was trying to fetch the branch master, which was redirected to main, but the second request resulted in a Bad credentials-error:

| GET https://api.github.com/repos/totycro/stacs/branches/master {'Authorization': 'token (oauth token removed)', 'User-Agent': 'PyGithub/Python'} None ==> 301 {'server': 'GitHub.com', 'date': 'Mon, 07 Feb 2022 14:16:04 GMT', 'content-type': 'application/json; charset=utf-8', 'content-length': '161', 'x-oauth-scopes': 'repo', 'x-accepted-oauth-scopes': '', 'github-authentication-token-expiration': '2022-05-08 12:28:34 UTC', 'location': 'https://api.github.com/repos/totycro/stacs/branches/main', 'x-github-media-type': 'github.v3; format=json', 'x-ratelimit-limit': '5000', 'x-ratelimit-remaining': '4966', 'x-ratelimit-reset': '1644244151', 'x-ratelimit-used': '34', 'x-ratelimit-resource': 'core', 'access-control-expose-headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset', 'access-control-allow-origin': '*', 'strict-transport-security': 'max-age=31536000; includeSubdomains; preload', 'x-frame-options': 'deny', 'x-content-type-options': 'nosniff', 'x-xss-protection': '0', 'referrer-policy': 'origin-when-cross-origin, strict-origin-when-cross-origin', 'content-security-policy': "default-src 'none'", 'vary': 'Accept-Encoding, Accept, X-Requested-With', 'x-github-request-id': '4044:ABCD:8C330:8E693:620129A4'} {"message":"Moved Permanently","url":"https://api.github.com/repos/totycro/stacs/branches/main","documentation_url":"https://docs.github.com/v3/#http-redirects"}
| GET https://api.github.com/repos/totycro/stacs/branches/main {'Authorization': 'token (oauth token removed)', 'User-Agent': 'PyGithub/Python'} None ==> 401 {'server': 'GitHub.com', 'date': 'Mon, 07 Feb 2022 14:16:04 GMT', 'content-type': 'application/json; charset=utf-8', 'content-length': '80', 'x-github-media-type': 'github.v3; format=json', 'x-ratelimit-limit': '60', 'x-ratelimit-remaining': '59', 'x-ratelimit-reset': '1644246964', 'x-ratelimit-used': '1', 'x-ratelimit-resource': 'core', 'access-control-expose-headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset', 'access-control-allow-origin': '*', 'strict-transport-security': 'max-age=31536000; includeSubdomains; preload', 'x-frame-options': 'deny', 'x-content-type-options': 'nosniff', 'x-xss-protection': '0', 'referrer-policy': 'origin-when-cross-origin, strict-origin-when-cross-origin', 'content-security-policy': "default-src 'none'", 'vary': 'Accept-Encoding, Accept, X-Requested-With', 'x-github-request-id': '4044:ABCD:8C389:8E6FE:620129A4'} {"message":"Bad credentials","documentation_url":"https://docs.github.com/rest"}

I'm now working around this by directly fetching main, but I guess the redirect from master is supposed work as well.

@stale stale bot removed the stale label Feb 7, 2022
@meangrape
Copy link

I'm having the same problem.
With curl:

  TOKEN=`cat gh_token`
  curl -v -H "Authorization: token $TOKEN" https://api.github.com/user

A portion of the HTML payload returned is:

{
"login": "meangrape",
"id": 9083,
"node_id": "MDQ6VXNlcjkwODM=",
"avatar_url": "https://avatars.githubusercontent.com/u/9083?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/meangrape",
"html_url": "https://github.com/meangrape",
"followers_url": "https://api.github.com/users/meangrape/followers",
"following_url":
"https://api.github.com/users/meangrape/following{/other_user}",
"gists_url": "https://api.github.com/users/meangrape/gists{/gist_id}",...

With the following python the error is BadCredentialsException: 401 {"message": "Bad credentials", "documentation_url": "https://docs.github.com/rest"}

from github import Github
token = open("gh_token").readline().strip()
g = Github(token)
print(g.get_user().get_orgs().totalCount)

with a stacktrace

BadCredentialsException                   Traceback (most recent call last)
Input In [26], in <module>
----> 1 print(g.get_user().get_orgs().totalCount)

File ~/Library/Caches/pypoetry/virtualenvs/gh-repo-perms-6skLoHCD-py3.10/lib/python3.10/site-packages/github/PaginatedList.py:149, in PaginatedList.totalCount(self)
    147 # set per_page = 1 so the totalCount is just the number of pages
    148 params.update({"per_page": 1})
--> 149 headers, data = self.__requester.requestJsonAndCheck(
    150     "GET", self.__firstUrl, parameters=params, headers=self.__headers
    151 )
    152 if "link" not in headers:
    153     if data and "total_count" in data:

File ~/Library/Caches/pypoetry/virtualenvs/gh-repo-perms-6skLoHCD-py3.10/lib/python3.10/site-packages/github/Requester.py:353, in Requester.requestJsonAndCheck(self, verb, url, parameters, headers, input)
    352 def requestJsonAndCheck(self, verb, url, parameters=None, headers=None, input=None):
--> 353     return self.__check(
    354         *self.requestJson(
    355             verb, url, parameters, headers, input, self.__customConnection(url)
    356         )
    357     )

File ~/Library/Caches/pypoetry/virtualenvs/gh-repo-perms-6skLoHCD-py3.10/lib/python3.10/site-packages/github/Requester.py:378, in Requester.__check(self, status, responseHeaders, output)
    376 output = self.__structuredFromJson(output)
    377 if status >= 400:
--> 378     raise self.__createException(status, responseHeaders, output)
    379 return responseHeaders, output

@EnricoMi
Copy link
Collaborator

EnricoMi commented Feb 13, 2022

token = open("gh_token").readline().strip()

With Python3, open does not use utf-8 encoding as default but whatever locale.getpreferredencoding() returns. Maybe the encoding of the file and that default encoding do not match up. But I presume the token is pretty ASCII, so that shouldn't be a problem. Did you inspect the content of open("gh_token").readline().strip()?

@EnricoMi
Copy link
Collaborator

I think it would be useful to set the log level to DEBUG, then the log would show what ulrllib3 is doing (including retries and redirects):

import logging
logging.root.level = logging.getLevelName('DEBUG')

@totycro
Copy link

totycro commented Feb 14, 2022

I see this in the log output. I guess the important part is where the request for the master branch returns a HTTP 301.

DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.github.com:443
DEBUG:urllib3.connectionpool:https://api.github.com:443 "GET /repos/totycro/stacs HTTP/1.1" 200 None
DEBUG:github.Requester:GET https://api.github.com/repos/totycro/stacs {'Authorization': 'token (oauth token removed)', 'User-Agent': 'PyGithub/Python'} None ==> 200 {'server': 'GitHub.com', 'date': 'Mon, 14 Feb 2022 08:24:45 GMT', 'content-type': 'application/json; charset=utf-8', 'transfer-encoding': 'chunked', 'cache-control': 'private, max-age=60, s-maxage=60', 'vary': 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With', 'etag': 'W/"c848b5c0015386e7a6698f5720a185fe6e7fc29d1599bad4553dd8bb87d5b201"', 'last-modified': 'Mon, 07 Feb 2022 14:12:32 GMT', 'x-oauth-scopes': 'repo', 'x-accepted-oauth-scopes': 'repo', 'github-authentication-token-expiration': '2022-05-08 12:28:34 UTC', 'x-github-media-type': 'github.v3; format=json', 'x-ratelimit-limit': '5000', 'x-ratelimit-remaining': '4977', 'x-ratelimit-reset': '1644830467', 'x-ratelimit-used': '23', 'x-ratelimit-resource': 'core', 'access-control-expose-headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset', 'access-control-allow-origin': '*', 'strict-transport-security': 'max-age=31536000; includeSubdomains; preload', 'x-frame-options': 'deny', 'x-content-type-options': 'nosniff', 'x-xss-protection': '0', 'referrer-policy': 'origin-when-cross-origin, strict-origin-when-cross-origin', 'content-security-policy': "default-src 'none'", 'content-encoding': 'gzip', 'x-github-request-id': '4020:48D9:1623731:1689D99:620A11CD'} {"id":456533757,"node_id":"R_kgDOGzYm_Q","name":"stacs","full_name":"totycro/stacs","private":true,"owner":{"login":"totycro","id":357581,"node_id":"MDQ6VXNlcjM1NzU4MQ==","avatar_url":"https://avatars.githubusercontent.com/u/357581?v=4","gravatar_id":"","url":"https://api.github.com/users/totycro","html_url":"https://github.com/totycro","followers_url":"https://api.github.com/users/totycro/followers","following_url":"https://api.github.com/users/totycro/following{/other_user}","gists_url":"https://api.github.com/users/totycro/gists{/gist_id}","starred_url":"https://api.github.com/users/totycro/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/totycro/subscriptions","organizations_url":"https://api.github.com/users/totycro/orgs","repos_url":"https://api.github.com/users/totycro/repos","events_url":"https://api.github.com/users/totycro/events{/privacy}","received_events_url":"https://api.github.com/users/totycro/received_events","type":"User","site_admin":false},"html_url":"https://github.com/totycro/stacs","description":null,"fork":false,"url":"https://api.github.com/repos/totycro/stacs","forks_url":"https://api.github.com/repos/totycro/stacs/forks","keys_url":"https://api.github.com/repos/totycro/stacs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/totycro/stacs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/totycro/stacs/teams","hooks_url":"https://api.github.com/repos/totycro/stacs/hooks","issue_events_url":"https://api.github.com/repos/totycro/stacs/issues/events{/number}","events_url":"https://api.github.com/repos/totycro/stacs/events","assignees_url":"https://api.github.com/repos/totycro/stacs/assignees{/user}","branches_url":"https://api.github.com/repos/totycro/stacs/branches{/branch}","tags_url":"https://api.github.com/repos/totycro/stacs/tags","blobs_url":"https://api.github.com/repos/totycro/stacs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/totycro/stacs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/totycro/stacs/git/refs{/sha}","trees_url":"https://api.github.com/repos/totycro/stacs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/totycro/stacs/statuses/{sha}","languages_url":"https://api.github.com/repos/totycro/stacs/languages","stargazers_url":"https://api.github.com/repos/totycro/stacs/stargazers","contributors_url":"https://api.github.com/repos/totycro/stacs/contributors","subscribers_url":"https://api.github.com/repos/totycro/stacs/subscribers","subscription_url":"https://api.github.com/repos/totycro/stacs/subscription","commits_url":"https://api.github.com/repos/totycro/stacs/commits{/sha}","git_commits_url":"https://api.github.com/repos/totycro/stacs/git/commits{/sha}","comments_url":"https://api.github.com/repos/totycro/stacs/comments{/number}","issue_comment_url":"https://api.github.com/repos/totycro/stacs/issues/comments{/number}","contents_url":"https://api.github.com/repos/totycro/stacs/contents/{+path}","compare_url":"https://api.github.com/repos/totycro/stacs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/totycro/stacs/merges","archive_url":"https://api.github.com/repos/totycro/stacs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/totycro/stacs/downloads","issues_url":"https://api.github.com/repos/totycro/stacs/issues{/number}","pulls_url":"https://api.github.com/repos/totycro/stacs/pulls{/number}","milestones_url":"https://api.github.com/repos/totycro/stacs/milestones{/number}","notifications_url":"https://api.github.com/repos/totycro/stacs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/totycro/stacs/labels{/name}","releases_url":"https://api.github.com/repos/totycro/stacs/releases{/id}","deployments_url":"https://api.github.com/repos/totycro/stacs/deployments","created_at":"2022-02-07T14:12:32Z","updated_at":"2022-02-07T14:12:32Z","pushed_at":"2022-02-08T15:29:11Z","git_url":"git://github.com/totycro/stacs.git","ssh_url":"git@github.com:totycro/stacs.git","clone_url":"https://github.com/totycro/stacs.git","svn_url":"https://github.com/totycro/stacs","homepage":null,"size":4,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":1,"license":null,"allow_forking":true,"is_template":false,"topics":[],"visibility":"private","forks":0,"open_issues":1,"watchers":0,"default_branch":"main","permissions":{"admin":true,"maintain":true,"push":true,"triage":true,"pull":true},"temp_clone_token":"AACXJTK4VNAS4LQVI5OD5ELCBIJPS","allow_squash_merge":true,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_auto_merge":false,"delete_branch_on_merge":false,"allow_update_branch":false,"network_count":0,"subscribers_count":1}
DEBUG:urllib3.connectionpool:https://api.github.com:443 "GET /repos/totycro/stacs/branches/master HTTP/1.1" 301 161
DEBUG:github.Requester:GET https://api.github.com/repos/totycro/stacs/branches/master {'Authorization': 'token (oauth token removed)', 'User-Agent': 'PyGithub/Python'} None ==> 301 {'server': 'GitHub.com', 'date': 'Mon, 14 Feb 2022 08:24:45 GMT', 'content-type': 'application/json; charset=utf-8', 'content-length': '161', 'x-oauth-scopes': 'repo', 'x-accepted-oauth-scopes': '', 'github-authentication-token-expiration': '2022-05-08 12:28:34 UTC', 'location': 'https://api.github.com/repos/totycro/stacs/branches/main', 'x-github-media-type': 'github.v3; format=json', 'x-ratelimit-limit': '5000', 'x-ratelimit-remaining': '4976', 'x-ratelimit-reset': '1644830467', 'x-ratelimit-used': '24', 'x-ratelimit-resource': 'core', 'access-control-expose-headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset', 'access-control-allow-origin': '*', 'strict-transport-security': 'max-age=31536000; includeSubdomains; preload', 'x-frame-options': 'deny', 'x-content-type-options': 'nosniff', 'x-xss-protection': '0', 'referrer-policy': 'origin-when-cross-origin, strict-origin-when-cross-origin', 'content-security-policy': "default-src 'none'", 'vary': 'Accept-Encoding, Accept, X-Requested-With', 'x-github-request-id': '4020:48D9:1623799:1689E0E:620A11CD'} {"message":"Moved Permanently","url":"https://api.github.com/repos/totycro/stacs/branches/main","documentation_url":"https://docs.github.com/v3/#http-redirects"}
DEBUG:urllib3.connectionpool:https://api.github.com:443 "GET /repos/totycro/stacs/branches/main HTTP/1.1" 401 80
DEBUG:github.Requester:GET https://api.github.com/repos/totycro/stacs/branches/main {'Authorization': 'token (oauth token removed)', 'User-Agent': 'PyGithub/Python'} None ==> 401 {'server': 'GitHub.com', 'date': 'Mon, 14 Feb 2022 08:24:46 GMT', 'content-type': 'application/json; charset=utf-8', 'content-length': '80', 'x-github-media-type': 'github.v3; format=json', 'x-ratelimit-limit': '60', 'x-ratelimit-remaining': '52', 'x-ratelimit-reset': '1644830537', 'x-ratelimit-used': '8', 'x-ratelimit-resource': 'core', 'access-control-expose-headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset', 'access-control-allow-origin': '*', 'strict-transport-security': 'max-age=31536000; includeSubdomains; preload', 'x-frame-options': 'deny', 'x-content-type-options': 'nosniff', 'x-xss-protection': '0', 'referrer-policy': 'origin-when-cross-origin, strict-origin-when-cross-origin', 'content-security-policy': "default-src 'none'", 'vary': 'Accept-Encoding, Accept, X-Requested-With', 'x-github-request-id': '4020:48D9:16237E4:1689E53:620A11CD'} {"message":"Bad credentials","documentation_url":"https://docs.github.com/rest"}

@EnricoMi
Copy link
Collaborator

This is so hard to reproduce. Can everyone who observes this issue here please give the following details:

  • which PyGithub version is affected?
  • do you access a public or private repository?
  • please create a bunch of tokens, do you see this error for all of them?
  • what are the permissions of those tokens?
  • example code that produces the error (if you haven't yet above)

@EnricoMi
Copy link
Collaborator

If someone who can reproduce the issue could please do the following:

  1. setup a virtual env: python -m virtualenv venv
  2. activate the virtual env: source venv/bin/activate
  3. install PyGithub: pip install PyGithub
  4. edit the Requester.py: nano venv/lib/python3.8/site-packages/github/Requester.py, replace in def __log:
elif requestHeaders["Authorization"].startswith("token"):
  headersForRequest["Authorization"] = "token (oauth token removed)"

with

elif requestHeaders["Authorization"].startswith("token"):
  import hashlib
  token = requestHeaders["Authorization"][5:]
  token = hashlib.md5(token.encode('utf-8')).hexdigest()
  headersForRequest["Authorization"] = f"token ({token})"
  1. run your code with github.enable_console_debug_logging():
import github
github.enable_console_debug_logging()
token = '...'
gh = github.Github(token)
gh.get_repo('totycro/stacs').get_branch('master')

Now, instead of

GET https://api.github.com/repos/totycro/stacs/branches/master {'Authorization': 'token (oauth token removed)', 'User-Agent': ...
GET https://api.github.com/repos/totycro/stacs/branches/main {'Authorization': 'token (oauth token removed)', 'User-Agent': ...

you should see:

GET https://api.github.com/repos/totycro/stacs/branches/master {'Authorization': 'token (f1224d3ef4c528fe4b982b5a5a923bac)', 'User-Agent': ...
GET https://api.github.com/repos/totycro/stacs/branches/main {'Authorization': 'token (f1224d3ef4c528fe4b982b5a5a923bac)', 'User-Agent': ...

Please verify that the hashes behind token are identical in all lines (here f1224d3ef4c528fe4b982b5a5a923bac).

@meangrape
Copy link

I don't have headersForRequest I have requestHeaders at line 638 so I went ahead and kept requestHeaders

elif requestHeaders["Authorization"].startswith("token"):
  headersForRequest["Authorization"] = "token (oauth token removed)"
elif requestHeaders["Authorization"].startswith("token"):
  requestHeaders["Authorization"] = "token (oauth token removed)"

My output doesn't consist of 2 lines, just the normal debug log info.

GET https://api.github.com/repos/totycro/stacs {'Authorization': 'token (bef1b10214a51913136de8faacd49238)', 'User-Agent': 'PyGithub/Python'} None ==> 401 {'server': 'GitHub.com', 'date': 'Tue, 15 Feb 2022 00:13:47 GMT', 'content-type': 'application/json; charset=utf-8', 'content-length': '80', 'x-github-media-type': 'github.v3; format=json', 'x-ratelimit-limit': '60', 'x-ratelimit-remaining': '14', 'x-ratelimit-reset': '1644884924', 'x-ratelimit-used': '46', 'x-ratelimit-resource': 'core', 'access-control-expose-headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset', 'access-control-allow-origin': '*', 'strict-transport-security': 'max-age=31536000; includeSubdomains; preload', 'x-frame-options': 'deny', 'x-content-type-options': 'nosniff', 'x-xss-protection': '0', 'referrer-policy': 'origin-when-cross-origin, strict-origin-when-cross-origin', 'content-security-policy': "default-src 'none'", 'vary': 'Accept-Encoding, Accept, X-Requested-With', 'x-github-request-id': 'DC1E:7635:53E695F:A5A7027:620AF03B'} {"message":"Bad credentials","documentation_url":"https://docs.github.com/rest"}

@Smok1e
Copy link

Smok1e commented Mar 9, 2022

Hay, I had the same problem just now and I just enabled all the checkboxes when creating the token, so the error stopped appearing

@meangrape
Copy link

I'd prefer not to give all permissions to a token that doesn't need them. Though I can try adding them one at a time and see if that helps.

@Smok1e
Copy link

Smok1e commented Mar 17, 2022

I'd prefer not to give all permissions to a token that doesn't need them. Though I can try adding them one at a time and see if that helps.

Actually, that was not the problem. Then I discovered that every time I made a commit, my token became invalid. This happened because the token was in one of the files in the repository.

@T-Dynamos
Copy link

Ohh

@nickderobertis
Copy link

nickderobertis commented Jun 23, 2022

I am also facing this error, and am able to consistently recreate it on 1.55 when all of these happen at once:

  • Logging level is set to DEBUG
  • The URL in question involves a redirect
  • A token is used

Here is my script that consistently recreates the issue:

from os import getenv
import logging

from github import Github

logging.basicConfig(
    level="DEBUG",
)

access_token = getenv("GITHUB_TOKEN")

gh = Github(access_token)
github_repo = gh.get_repo("whoopnip/check-if-issue-exists-action")

print(github_repo.name)

When running this script with GITHUB_TOKEN=ghp... then it fails. Without a token, it succeeds. Removing debug logging, it succeeds either way. And when switching the url to nickderobertis/check-if-issue-exists-action (the url it is redirecting to), then it also succeeds.

@chmnrathee
Copy link

When it gives "message": "Bad credentials", There can be only one possible reasons: you token is not valid. Most probably, your are not setting it properly.

Would suggest you to use below one command:
g = Github(base_url="https://<github_server_name>/api/v3/", login_or_token="<token_value>")

then try it.

@ShiftedMr
Copy link

ShiftedMr commented Aug 12, 2022

Hey folks!! I figured it out; at least in my instance.
For those of you still getting this error, React to this if your org is private.

It looks like it has to do with a minor change with how the python requests library handles auth headers. You can't specify the header manually as there's a special location that needs to be updated.

self.headers = headers

For a test I inserted this below that line:

        for header in self.headers:
            if str(self.headers[header]).startswith('token'):
                self.session.auth=(tuple(self.headers[header].split(' ')))

This is based on how auth is set here for sessions: https://requests.readthedocs.io/en/latest/user/advanced/
and here for non sessions: https://requests.readthedocs.io/en/latest/user/authentication/

I'm not sure why the auth header is overwritten when auth is unset BUT it's got me working right now. Going to see if I can work on a patch

Edit 1:
While I don't have a netrc file; the cause may be related : https://requests.readthedocs.io/en/latest/user/authentication/#netrc-authentication

Edit 2:
This is where it started checking directories OUTside the script for netrc
psf/requests@ba54371#diff-b4b6ac4e508ec3e142b8b8a3cf33b9ab925e3bdd03dbbe859384760746b1ac76R172

Which time wise matches when this issue started

@efstratios97
Copy link

efstratios97 commented Aug 15, 2022

I had the same issue. The first time when I used my token everything is fine. Then it did not work anymore. Again I created a new token (all permissions checked) and the first time it worked then again not. Later I checked my emails and saw that Github send me a notification, where they automatically revoked my developer token because they found it in a git commit. (I made a test commit with my original token as an input test). Maybe some of you people did the same as me, so the token got revoked.
However, I don't know if this is the only cause for such an error but maybe for some of you.

@ShiftedMr I checked out your PR and unfortunately, it couldn't solve the above-described scenario, where Github revokes the token automatically.

@ShiftedMr
Copy link

@efstratios97 My issue was not related to the revoked token.
I captured the issue my and my teammates were running into.
The issue only raises itself on non public repos as with public repos it doesn't need a valid auth header

@injaelee
Copy link

For me, the issue was the ~/.netrc file. Contents in the file were being picked up even when the token is passed to the Gitthub constructor.

Solution:

  • Try deleting or updating ~/.netrc accordlingly

@claird160
Copy link

Me, too.

I'd been successfully using PyGitHub for a couple hours of programming. Then, running the same two-line script that had worked previously, github.GithubException.BadCredentialsException erupted. curl ... with the same token works. gh ... with the same token now fails as well.

I have no ~/.netrc. I haven't changed Python version, venv, pygithub version, ... I can see the unaltered token going out in the header.

@zealotous
Copy link

zealotous commented Dec 29, 2022

I had the same issue with github api and python requests.

$ grep 'github.com' ~/.netrc -A 2
machine api.github.com
login ***
password ***

Removing the entry fixes the issue.

BasicAuth or a custom authenication class can be used if you have no controll over .netrc file:

import requests

GITHUB_KEY = "ghp_***"

class BearerAuthToken(requests.auth.AuthBase):
    def __call__(self, r):
        r.headers['Authorization'] = f'Bearer {GITHUB_KEY}'
        return r


requests.post(
        "https://api.github.com/graphql",
        json={"query": "query { viewer { login }}"}},
        auth=BearerAuthToken() ,
)

@raghualuva
Copy link

I almost spent ~2 hours trying to figure out the issue. As @injaelee said, I have removed the github related entries in .netrc file and i was able to use ghe_instance.get_user().get_repo().delete() to delete the repo.

@joshxyzhimself
Copy link

Happened to me when I accidentally committed the token. GitHub immediately revoked it.

@yeputons
Copy link

yeputons commented Sep 26, 2023

I've just had a very similar issue myself:

  1. The code was working just fine for the last few days and making requests to GitHub API via Pygithub. Almost a thousand requests in a short time span.
  2. Last night it started sporadically (sic!) crashing with Bad Credentials when trying to list comments of a PR. Not secondary limits, not primary limits. It never crashed right away, always after some number of requests on different PRs. The same sequence of GET requests, same requests sometimes working or crashing. Crash was consistently within first ~100 requests (not sure exactly, though).
  3. Re-creation of the access token did not help.

What helped: do not use "Fine-grained tokens" that are still in beta, use "Tokens (classic)". Now everything works like a charm.

Feels like a GitHub bug.

@Lslightly
Copy link

Lslightly commented Apr 22, 2024

I've just had a very similar issue myself:

  1. The code was working just fine for the last few days and making requests to GitHub API via Pygithub. Almost a thousand requests in a short time span.
  2. Last night it started sporadically (sic!) crashing with Bad Credentials when trying to list comments of a PR. Not secondary limits, not primary limits. It never crashed right away, always after some number of requests on different PRs. The same sequence of GET requests, same requests sometimes working or crashing. Crash was consistently within first ~100 requests (not sure exactly, though).
  3. Re-creation of the access token did not help.

What helped: do not use "Fine-grained tokens" that are still in beta, use "Tokens (classic)". Now everything works like a charm.

Feels like a GitHub bug.

I have similar use case but the difference is that I use classic tokens instead of fine-grained ones. My program also worked fine for the last few days but failed when my token expired and I regenerated my token.

I use pickle library to cache the objects since getting objects is time-consuming. Then my token expired. These days, I use a regenerated token and load objects through pickle library. Since these objects contains expired token, Bad credentials will definitely occur when I use these objects to visit some new properties.

So the core problem to me is the cached old objects containning expired tokens.

The reproducible code is in https://github.com/Lslightly/demo-BadCredentials

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.