Accessing GitHub's REST API with CURL

REST APIs are popular. First mentioned in Roy Fielding’s dissertation
it describes an architecture based on the World Wide Web. REST APIs
expose resources. The main prerequisites of good REST APIs are

  1. Addressability Every resource is addressable via an uniform
    resource identifier
    .
  2. Uniform Interface HTTP is used as an interface to the manipulated
    the various resources the API exposes.
  3. Interconnectednes Just like website are linked together,
    resources should provide links to related resources.
  4. Statelessness The server should be kept stateless. Any state that
    is necessary should be passed along the request.

When discussing RESTful APIs it good to have some real world
examples. REST APIs that are accessible via the internet often shield
their interface behind some form of authentication. This adds a
barrier to using these API as demonstration.

In this blog post we will show how to use GitHub REST API, that needs
authentication, from the command line using curl.

1 GitHub

GitHub is a hosting service for git repositories. GitHub’s logo sports
the sub-title “social coding” and it can be seen as a social network
for developers.

The GitHub’s REST API documentation can be found at
https://developer.github.com/v3/.

2 EndPoints

We can request all the endpoints that GitHub exposes with the
following command

curl -X GET https://api.github.com
{
  "current_user_url": "https://api.github.com/user",
  "authorizations_url": "https://api.github.com/authorizations",
  "code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}",
  "emails_url": "https://api.github.com/user/emails",
  "emojis_url": "https://api.github.com/emojis",
  "events_url": "https://api.github.com/events",
  "feeds_url": "https://api.github.com/feeds",
  "following_url": "https://api.github.com/user/following{/target}",
  "gists_url": "https://api.github.com/gists{/gist_id}",
  "hub_url": "https://api.github.com/hub",
  "issue_search_url": "https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}",
  "issues_url": "https://api.github.com/issues",
  "keys_url": "https://api.github.com/user/keys",
  "notifications_url": "https://api.github.com/notifications",
  "organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}",
  "organization_url": "https://api.github.com/orgs/{org}",
  "public_gists_url": "https://api.github.com/gists/public",
  "rate_limit_url": "https://api.github.com/rate_limit",
  "repository_url": "https://api.github.com/repos/{owner}/{repo}",
  "repository_search_url": "https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}",
  "current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}",
  "starred_url": "https://api.github.com/user/starred{/owner}{/repo}",
  "starred_gists_url": "https://api.github.com/gists/starred",
  "team_url": "https://api.github.com/teams",
  "user_url": "https://api.github.com/users/{user}",
  "user_organizations_url": "https://api.github.com/user/orgs",
  "user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}",
  "user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"
}

It returns a JSON object with endpoints that can be requested.

3 Authentication

Let’s request the current user. From the endpoints listed in the
preceding section we know that we have to request https://api.github.com/user

curl -X GET https://api.github.com/user
{
  "message": "Requires authentication",
  "documentation_url": "https://developer.github.com/v3"
}

Unfortunatly the server sends back a status code of 401 Unauthorized
with the preceding message.

3.1 Basic Authentication

One way of authenticating is via basic authentication as specified in
RFC2617. In this form of authentication you provide the GitHub
username and password. Using curl one can pass in the -u or --user
option, providing the corresponding password with each request.

The following excerpt shows a session demonstrating this option.

> curl -X GET -u dvberkel https://api.github.com/user/dvberkel
Enter host password for user 'dvberkel':

This option soon becomes tiresome, especially if you have a password
with high entropy. To mitigate the burden one could create environment
variables and use them instead.

For example, if you create the following environment variables

export GITHUB_USER=dvberkel
export GITHUB_PASSWORD=abcd1234

A request could be made with the command

curl -X GET -u $GITHUB_USER:$GITHUB_PASSWORD https://api.github.com/user

With the result

{
  "login": "dvberkel",
  "id": 493347,
  "avatar_url": "https://avatars.githubusercontent.com/u/493347?v=3",
  "gravatar_id": "",
  "url": "https://api.github.com/users/dvberkel",
  "html_url": "https://github.com/dvberkel",
  "followers_url": "https://api.github.com/users/dvberkel/followers",
  "following_url": "https://api.github.com/users/dvberkel/following{/other_user}",
  "gists_url": "https://api.github.com/users/dvberkel/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/dvberkel/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/dvberkel/subscriptions",
  "organizations_url": "https://api.github.com/users/dvberkel/orgs",
  "repos_url": "https://api.github.com/users/dvberkel/repos",
  "events_url": "https://api.github.com/users/dvberkel/events{/privacy}",
  "received_events_url": "https://api.github.com/users/dvberkel/received_events",
  "type": "User",
  "site_admin": false,
  "name": "Daan van Berkel",
  "company": null,
  "blog": "http://dvberkel.github.com",
  "location": null,
  "email": "daan.v.berkel.1980@gmail.com",
  "hireable": false,
  "bio": null,
  "public_repos": 221,
  "public_gists": 25,
  "followers": 22,
  "following": 63,
  "created_at": "2010-11-23T12:41:08Z",
  "updated_at": "2014-11-24T10:13:01Z",
  "private_gists": 0,
  "total_private_repos": 3,
  "owned_private_repos": 2,
  "disk_usage": 225912,
  "collaborators": 1,
  "plan": {
    "name": "small",
    "space": 1228800,
    "collaborators": 0,
    "private_repos": 10
  }
}

3.2 2-Factor Authentication

GitHub supports 2-Factor Authentication which provides additional
security. If a user uses 2-factor authentication basic authentication
is a bit more complicated. When firing a request

curl -X GET -u $GITHUB_USER:$GITHUB_PASSWORD https://api.github.com/user

The server responds with

{
  "message": "Must specify two-factor authentication OTP code.",
  "documentation_url": "https://developer.github.com/v3/auth#working-with-two-factor-authentication"
}

Furthermore, it sends back a X-GitHub-OTP: required; app or
X-GitHub-OTP: require; sms header, indicating that a one-time
password
is required. These one-time passwords are acquired via an
app, such as Google Authenticator, or via SMS.

Once required, the one-time password needs to be send via the
X-GitHub-OTP header as shown in the code below.

curl -X GET -u $GITHUB_USER:$GITHUB_PASSWORD -H 'X-GitHub-OTP: 123456' https://api.github.com/user

3.3 Access Token

Providing a username and password with every request soon becomes
tiresome. Especially for users that have enabled 2-factor
authentication.

Although the burden is somewhat alliviated by introducing environment
variables, this introduces the possibillity that someone gleans your
password by taking control of your computer and echoing these
variables.

> echo $GITHUB_PASSWORD
abcd1234

GitHub offers other means of authentication. One of them are access
tokens
. Access tokens are very well suited for our use case,
i.e. accessing the GitHub REST API from the command line.

An access token can be easily created and revoked. It offers
fine-grained access controlled via scopes.

3.3.1 Web Interface

One way to create access tokens is via the web interface that GitHub
offers. You access it by clicking the settings icon.

userbar-account-settings.png

Next you have to select Applications

applications.png

and create a new token

generate_new_token.png

You can give the access token a meaningfull description so you can
differentiate it in the list of all access tokens.

token_description.png

Furthermore you can selectively pick scopes to control what this token
gives access to.

token_scopes.gif

When you finally click Generate token

generate_token.png

your access token will be shown.

personal_access_tokens.png

For security reasons this is the only time you the access token is
shown. The next time you visit the page it will only show the
description.

You should treat access tokens as if they are passwords. Do not share
them easily and make sure to remove unused access tokens often.

3.3.2 REST API

An other way of creating access tokens is via the Authorizations
API
. You access the Authorization API with basic authentication as
explained above. For example the code below generates an access token
to the users email address.

curl 
  -X POST 
  -u $GITHUB_USER:$GITHUB_PASSWORD 
  -H 'Content-Type: application/json' 
  -d '{"scopes": ["user:email"],"note": "blog example"}' 
  https://api.github.com/authorizations

We send along our credentials and data. The data is send as JSON so we
add an appropriate HTTP header. The result send back includes our
access token.

{
  "id": 12345678,
  "url": "https://api.github.com/authorizations/12345678",
  "app": {
    "name": "blog example (API)",
    "url": "https://developer.github.com/v3/oauth_authorizations/",
    "client_id": "00000000000000000000"
  },
  "token": "1234567890abcdef1234567890abcdef12345678",
  "note": "blog example",
  "note_url": null,
  "created_at": "2014-11-25T11:50:17Z",
  "updated_at": "2014-11-25T11:50:17Z",
  "scopes": [
    "user:email"
  ]
}

Just like our username and password, the token can be stored in an
environment variable for ease access

export GITHUB_TOKEN=1234567890abcdef1234567890abcdef12345678

When we are done with the token we can use the API to delete the token
as well.

curl -X DELETE -u $GITHUB_USER:$GITHUB_PASSWORD https://api.github.com/authorizations/12345678

3.4 Using Acces Token

With our access token nicely tucked away in our environment variable
GITHUB_TOKEN we can start using it. We will use the access token to
retrieve the user information.

Instead of passing along the username and password we pass along our
access token and a password of x-oauth-basic.

curl -X GET -u $GITHUB_TOKEN:x-oauth-basic 'https://api.github.com/user'
{
  "login": "dvberkel",
  "id": 493347,
  "avatar_url": "https://avatars.githubusercontent.com/u/493347?v=3",
  "gravatar_id": "",
  "url": "https://api.github.com/users/dvberkel",
  "html_url": "https://github.com/dvberkel",
  "followers_url": "https://api.github.com/users/dvberkel/followers",
  "following_url": "https://api.github.com/users/dvberkel/following{/other_user}",
  "gists_url": "https://api.github.com/users/dvberkel/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/dvberkel/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/dvberkel/subscriptions",
  "organizations_url": "https://api.github.com/users/dvberkel/orgs",
  "repos_url": "https://api.github.com/users/dvberkel/repos",
  "events_url": "https://api.github.com/users/dvberkel/events{/privacy}",
  "received_events_url": "https://api.github.com/users/dvberkel/received_events",
  "type": "User",
  "site_admin": false,
  "name": "Daan van Berkel",
  "company": null,
  "blog": "http://dvberkel.github.com",
  "location": null,
  "email": "daan.v.berkel.1980@gmail.com",
  "hireable": false,
  "bio": null,
  "public_repos": 221,
  "public_gists": 25,
  "followers": 22,
  "following": 63,
  "created_at": "2010-11-23T12:41:08Z",
  "updated_at": "2014-11-25T08:12:06Z",
  "private_gists": 0,
  "total_private_repos": 3,
  "owned_private_repos": 2,
  "disk_usage": 225912,
  "collaborators": 1,
  "plan": {
    "name": "small",
    "space": 1228800,
    "collaborators": 0,
    "private_repos": 10
  }
}

4 Conclusion

Demonstrating GitHub’s REST API from the command line is fairly
easy. By creating an access token and storing it in an environment
variable curl can be used to access the resources that GitHub exposes.