OAuth authentication without a browser

For some time now I've been struggling with an OAuth authorisation. Every time I wanted to build console app that uses service secured be OAuth I had the same problem - how to provide user name and password without a browser. After some research I came up with an idea - I can use headless browser and Selenium to interact with it.

As it turned out my solution was extremely easy to implement and the basic prototype is less than 40 lines of code. Here's step-by-step guide for Python.

1. Install headless browser.

I'm using MacBook as my dev machine so I needed to install geckodriver that I will use later as a Selenium's Firefox driver.

$ brew install geckodriver

2. Create virtualenv and install required libraries.

For managing virtualenvs and packages I'm using pipenv.

$ mkdir /some/path/to/project && cd /some/path/to/project
$ pipenv --python 3.6
$ pipenv install selenium
$ pipenv install requests

3. Authorise/register your app.

For the purpose of this example I have used Pocket API. In order to use it I needed to obtain consumer key. Depending on a service API, requirements for registering your app may vary, so I will skip describing this step.

4. Enjoy OAuth authorisation using headless browser and Selenium.

As I mentioned before I've been using Pocket API as my test case. The code you see below is just a concept and if you want to use it you need to add few things like checking response code, handle errors/exceptions or caching responses (when sending multiple requests to the API we can save access token and skip authorisation process, which is slow) and definitely remove user name, password and pocket_key (or any other token you use) from the code. Below the code you will find explanation for each marked block.

from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
from selenium import webdriver
import os
import sys
import requests
from urllib.parse import parse_qs

# 1
firefox_bin = '/usr/local/bin/geckodriver'
pocket_key = '12345-abcd12345abcd12345'
redirect_uri = 'pocketapp1234:authorizationFinished'
user = 'user_name'
password = 'user_pass'

# 2
os.environ['MOZ_HEADLESS'] = '1'

# 3
r1 = requests.post('https://getpocket.com/v3/oauth/request', data={
    'consumer_key': pocket_key,
    'redirect_uri': redirect_uri
})
request_token = parse_qs(r1.text)['code'][0]

# 4
binary = FirefoxBinary(firefox_bin, log_file=sys.stdout)

driver = webdriver.Firefox()
driver.get('https://getpocket.com/auth/authorize?request_token={}&redirect_uri={}'.format(request_token, redirect_uri))
driver.find_element_by_id('feed_id').send_keys(user)
driver.find_element_by_id('login_password').send_keys(password)
driver.find_element_by_class_name('btn-authorize').click()
driver.close()

# 5
r2 = requests.post('https://getpocket.com/v3/oauth/authorize', data={
    'consumer_key': pocket_key,
    'code': request_token
})
access_token = parse_qs(r2.text)['access_token'][0]

# 6
r_data = requests.post('https://getpocket.com/v3/get', data={
    'consumer_key': pocket_key,
    'access_token': access_token
})
json = r_data.json()

# 1

Set up all required data. This should go to some kind on config file.

# 2

For Firefox we need to set headless flag to inform browser not to use gui.

# 3

In order to be able to authorise with Pocket API we need to obtain request token.

# 4

We are using Selenium to send login form with obtained in previous step request token. Keep in mind that different providers will have different login form. You need to check field ids you will be populating with data.

# 5

Fetch access token from Pocket API. This token will be used in all subsequent requests.

# 6

Use Pocket (or any other) API in your console application.