May 27, 2016

OAuth 1.0a for ETrade API using Haskell

Lately, I have been interested in creating a market quotes receiver which later on, using a DB and ML algorithms, will analyze the market movements and provide suggestions for better investments. But initially, I had to connect to an API provided, for which I found ETrade, which is an Online Broker stablished in the USA which has a REST API to request quotes, account data or even place orders to the Stock Exchange.

I have been long time fan of Haskell, but I had never engaged with a long term project to learn more about the language, the platform and all its possibilities, although I have a book pending to continue writing about Haskell FRP for Game development, this project seems like a good time/learn investment for long term goals: apply category theory, learn to trade stocks, explore Machine Learning algorithms and many more.

For all that, I started doing authentication to the platform, considering that I had to email ETrade customer service to request a Auth public/private key, then later with those in file, I created this script which will perform all necessary steps to retrieve a access token, which would allow me to query everything within the Sandbox: so here is the code:

{-# LANGUAGE OverloadedStrings #-}

module Network.Authenticate.ETradeOAuth
where

import           Data.ByteString         as BS
import           Data.ByteString.Char8   as C8 (pack, unpack)
import           Data.List               as ML
import           Data.Maybe
import           Network.HTTP.Client
import           Network.HTTP.Client.TLS
import           Network.HTTP.Types.URI
import           System.Environment
import           System.IO               ()
import           Web.Authenticate.OAuth
import           Web.Browser

authToken :: ByteString
authToken = "oauth_token"

-- OAuth Object to use when communicating with
-- ETrade developer API. Also, it must define
-- the two fields: oauthConsumerKey and oauthConsumerSecret
-- to be able to stablish a succesful communication.
-- For more information:
-- https://developer.etrade.com/ctnt/dev-portal/getArticleByCategory?category=Documentation
oa :: OAuth
oa = newOAuth {
    oauthServerName      = "ETrade"
  , oauthRequestUri      = "https://etws.etrade.com/oauth/request_token"
  , oauthAccessTokenUri  = "https://etws.etrade.com/oauth/access_token"
  , oauthAuthorizeUri    = "https://us.etrade.com/e/t/etws/authorize"
  , oauthSignatureMethod = HMACSHA1
  , oauthVersion         = OAuth10a
  , oauthConsumerKey     = undefined
  , oauthConsumerSecret  = undefined
  , oauthCallback        = Just "oob"
  }

-- We use a tlsManager to communicate using HTTPS
mgr :: IO Manager
mgr = newManager tlsManagerSettings

-- OAuth can construct automatically the authorization
-- URL, however ETrade uses another structure, which
-- is depicted as follows:
-- https://us.etrade.com/e/t/etws/authorize?key={oauth_consumer_key}&token={oauth_token}
etradeAuthUri :: OAuth -> Credential -> ByteString
etradeAuthUri oa crd = BS.concat [authUri, "?key=", consumerKey, "&token=", token]
    where
        authUri = C8.pack $ oauthAuthorizeUri oa
        token = urlEncode True $ findAuth $ unCredential crd
        consumerKey = urlEncode True $ oauthConsumerKey oa

-- Helper function to find the OAuth token out of a temporal credential
findAuth :: [(ByteString, ByteString)] -> ByteString
findAuth x = snd $ fromJust findAction
    where
        findFunction a = authToken == fst a
        findAction = ML.find findFunction x

-- This will innitiate the OAuth process, using an OAuth object and
-- a TLS Manager to asking for a temporary credential, with which it
-- will redirect the user to a ETrade webpage in which access is
-- authorized and a confirmation string is provided
doInitialAuth :: OAuth -> Manager -> IO Credential
doInitialAuth oa m = do
    temp <- --="" -="" ::="" able="" access="" all="" an="" are="" as="" authurl="" c8.unpack="" confirmation="" doaccessauth="" for="" gettemporarycredential="" in="" let="" m="" make="" oa="" oauth="" openbrowser="" place="" platform="" request="" return="" string="" subsequently="" temp="" the="" to="" token="" use="" we="" which="" will="" with=""> Manager -> ByteString -> Credential -> IO Credential
doAccessAuth oa m verifier tempCred = getAccessToken oa cred m
    where cred = injectVerifier verifier tempCred

main :: IO Credential
main = do
    print "This script will request an OAuth Token and Open the browser"
    m <- a="" answer="" bs.getline="" bs.null="" code="" confirm="" confirmation="" continue="" cred="" do="" doaccessauth="" doinitialauth="" else="" if="" m="" mgr="" newcredential="" oa="" ot="" print="" requests:="" return="" rovide="" string="" temp="" the="" then="" to="" uth="" valid="">

Of course, the source could be found in Github with a GPL v3 license.

In general terms, we need to install and import the following libraries:

  • bytestring
  • authenticate-oauth
  • http-client
  • http-client-tls
  • open-browser

With those already in place, it would be as simple as replace the values "oauthConsumerKey" and "oauthConsumerSecret" for those provided by ETrade or any other OAuth 1.0a service. Of course, if using other service, request URIs needs to be replaced.

After that, it is as simple as running this line:
runhaskell ETrade-OAuth-Example.hs.hs

That will automatically run main, which creates a TLS manager, request a token, opens the native OS browser to authorize the application and waits for the access code to finally request an access token from the API. All these steps were simplified using the OAuth library and the code snippet.

Of course, I am open to suggestion on how to improve this code is handled to make it more approachable to other interests.

Further Reading:

Github Source Code Link
Etrade Developer Documentation
OAuth Authentication process

No comments:

Post a Comment