OAuth authentication without a browser

Sun 07 July 2019

For some time now I've been struggling with an OAuth authorisation. Every time I wanted to build console app that uses service secured with 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 a 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.

Install a 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

Create virtual environment and install required libraries

For managing virtual envs I'm using pyenv. You can use any virtual environment tool.

pyenv virtualenv 3.8.0 oauth-venv
pyenv local oauth-venv

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.

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.

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

# Set up all required data. Sensitive data should be fetched from env vars.
firefox_bin = '/usr/local/bin/geckodriver'
pocket_key = '12345-abcd12345abcd12345'
redirect_uri = 'pocketapp1234:authorizationFinished'
user = 'user_name'
password = 'user_pass'

# For Firefox we need to set headless flag to inform browser not to use gui.
os.environ['MOZ_HEADLESS'] = '1'

# In order to be able to authorise with Pocket API we need to obtain request token.
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]

# 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.
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()

# Fetch access token from Pocket API. This token will be used in all subsequent requests.
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]

# Use Pocket (or any other) API in your console application.
r_data = requests.post('https://getpocket.com/v3/get', data={
    'consumer_key': pocket_key,
    'access_token': access_token
})
json = r_data.json()