Generate a Github OAuth2 Token

There are two ways to authenticate with the GitHub API: HTTP basic auth, and OAuth2. [1] It is preferable to use OAuth2, so your script can run without user input, and without storing your password.

The OAauth2 token can be sent in the request header, or as a parameter. We will send it as a header in later examples.

POST Request

First, we will prompt the user for username/password, and compose a POST request to the API. The request format is documented in the OAuth section of the Github API docs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
GITHUB_API = 'https://api.github.com'


import requests
import json
from urlparse import urljoin


def main():
    #
    # User Input
    #
    username = raw_input('Github username: ')
    password = raw_input('Github password: ')
    #
    # Compose Request
    #
    url = urljoin(GITHUB_API, 'authorizations')
    payload = {}
    res = requests.post(
        url,
        auth = (username, password),
        data = json.dumps(payload),
        )
    print res.text

if __name__ == '__main__':
    main()

Let’s give it a try:

(class)$ python authtoken.py
Github username: jmcvetta
Github password: fooba

That’s not good - our password is shown when we type it!

Password Privacy

We can protect the user’s privacy while inputting their password with the getpass library. While we’re at it, we can prompt the user for an optional note to describe how this token will be used.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
GITHUB_API = 'https://api.github.com'


import requests
import getpass
import json
from urlparse import urljoin


def main():
    #
    # User Input
    #
    username = raw_input('Github username: ')
    password = getpass.getpass('Github password: ')
    note = raw_input('Note (optional): ')
    #
    # Compose Request
    #
    url = urljoin(GITHUB_API, 'authorizations')
    payload = {}
    if note:
        payload['note'] = note
    res = requests.post(
        url,
        auth = (username, password),
        data = json.dumps(payload),
        )
    print res.text

if __name__ == '__main__':
    main()

Let’s give it a try:

(class)$ python authtoken.py
Github username: jmcvetta
Github password:
Note (optional): admin script
{"scopes":[],"note_url":null,"created_at":"2012-10-21T05:32:30Z","url":"https://api.github.com/authorizations/744660","app":{"url":"http://developer.github.com/v3/oauth/#oauth-authorizations-api","name":"admin script (API)"},"updated_at":"2012-10-21T05:32:30Z","token":"a977026974077e83e593744aa9308422e92a26bd","note":"admin script","id":744660}

Seems to have worked! The response is a big JSON blob.

JSON Parsing

We can parse the JSON response and provide just the token, in nice human-readable form, to the user.

Explore the response data by setting a breakpoint and running our program in the debugger. Start by parsing res.text into JSON, and examining the keys.

The token lives in the creatively-named field token. We will extract it and print it for the user.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
GITHUB_API = 'https://api.github.com'


import requests
import getpass
import json
from urlparse import urljoin


def main():
    #
    # User Input
    #
    username = raw_input('Github username: ')
    password = getpass.getpass('Github password: ')
    note = raw_input('Note (optional): ')
    #
    # Compose Request
    #
    url = urljoin(GITHUB_API, 'authorizations')
    payload = {}
    if note:
        payload['note'] = note
    res = requests.post(
        url,
        auth = (username, password),
        data = json.dumps(payload),
        )
    #
    # Parse Response
    #
    j = json.loads(res.text)
    token = j['token']
    print 'New token: %s' % token

if __name__ == '__main__':
    main()

Let’s give it a try:

(class)$ python authtoken.py
Github username: jmcvetta
Github password:
Note (optional): admin script
New token: 9c0e2ab295ee0e92130142ad3c90bbf5fe93642f

Bingo - it worked!

Error Handling

But what if we don’t type the right username/password combo?

(class)$ python authtoken.py
Github username: jmcvetta
Github password:
Note (optional):
Traceback (most recent call last):
  File "authtoken.2.py", line 46, in <module>
    main()
  File "authtoken.2.py", line 42, in main
    token = j['token']
KeyError: 'token'

Gross.

Once again we can run our program in the debugger to explore the server’s response. It looks like we have a res.status_code of 401. Any HTTP response code 400 or above indicates an error. It also looks like the server helpfully provides a message field with an error message.

We can look for response codes >= 400 and present the user a friendly error message:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
GITHUB_API = 'https://api.github.com'


import requests
import getpass
import json
from urlparse import urljoin


def main():
    #
    # User Input
    #
    username = raw_input('Github username: ')
    password = getpass.getpass('Github password: ')
    note = raw_input('Note (optional): ')
    #
    # Compose Request
    #
    url = urljoin(GITHUB_API, 'authorizations')
    payload = {}
    if note:
        payload['note'] = note
    res = requests.post(
        url,
        auth = (username, password),
        data = json.dumps(payload),
        )
    #
    # Parse Response
    #
    j = json.loads(res.text)
    if res.status_code >= 400:
        msg = j.get('message', 'UNDEFINED ERROR (no error description from server)')
        print 'ERROR: %s' % msg
        return
    token = j['token']
    print 'New token: %s' % token

if __name__ == '__main__':
    main()

Let’s give it a try:

(class)$ python authtoken.py
Github username: jmcvetta
Github password:
Note (optional):
ERROR: Bad credentials

Now we have a friendly, useful program.

Footnotes

[1]http://developer.github.com/v3/#authentication