25

E-Trade released their API recently and provided technical documentation which is somewhat useful but not complete.

Does anyone have a fully working example in C# that shows how this works?

I have been able to do the authentication using OAuth correctly, but when it comes to getting information out of my account or market data, the servers fail.

George Stocker
  • 57,289
  • 29
  • 176
  • 237
Maher
  • 481
  • 1
  • 6
  • 12
  • Did you ever figure anything out on this? – Jeff Nov 02 '10 at 18:05
  • I'm still trying to get the Access_Token to work. I am able to do the Request_Token, but get "Invalid Signature" when sending up the Access_Token request. I believe it is because the hashing of the key (consumer_secret & token_secret) is getter than 64 characters. Once I get around this problem, I will be at same point you are at. -Pat –  Jan 17 '11 at 03:08

5 Answers5

9

I was able to connect using the DevDefined OAuth Library, but i had to make some tweeks to the source to get it to work properly. I forked the repo so you can download the src i used, and build you a .dll.

Repo: GitHub

Example Class:

 public abstract class BaseOAuthRepository
{

    private static string REQUEST_URL = "https://etws.etrade.com/oauth/request_token";
    private static string AUTHORIZE_URL = "https://us.etrade.com/e/t/etws/authorize";
    private static string ACCESS_URL = "https://etws.etrade.com/oauth/access_token";

    private readonly TokenBase _tokenBase;
    private readonly string _consumerSecret;

    protected BaseOAuthRepository(TokenBase tokenBase, 
                                  string consumerSecret)
    {
        _tokenBase = tokenBase;
        _consumerSecret = consumerSecret;
    }

    public TokenBase MyTokenBase
    {
        get { return _tokenBase; }
    }

    public string MyConsumerSecret
    {
        get { return _consumerSecret; }
    }


    public OAuthSession CreateSession()
    {
        var consumerContext = new OAuthConsumerContext
        {
            ConsumerKey = MyTokenBase.ConsumerKey,
            ConsumerSecret = MyConsumerSecret,
            SignatureMethod = SignatureMethod.HmacSha1,
            UseHeaderForOAuthParameters = true,
            CallBack = "oob"
        };

        var session = new OAuthSession(consumerContext, REQUEST_URL, AUTHORIZE_URL, ACCESS_URL);    
        return session;
    }

    public IToken GetAccessToken(OAuthSession session)
    {
        IToken requestToken = session.GetRequestToken();
        string authorizationLink = session.GetUserAuthorizationUrlForToken(MyTokenBase.ConsumerKey, requestToken);
        Process.Start(authorizationLink);
        Console.Write("Please enter pin from browser: ");
        string pin = Console.ReadLine();
        IToken accessToken = session.ExchangeRequestTokenForAccessToken(requestToken, pin.ToUpper());

        return accessToken;
    }

    public string GetResponse(OAuthSession session, string url)
    {
        IToken accessToken = MyTokenBase;

        var response = session.Request(accessToken).Get().ForUrl(url).ToString();
        return response;
    }

    public XDocument GetWebResponseAsXml(HttpWebResponse response)
    {
        XmlReader xmlReader = XmlReader.Create(response.GetResponseStream());
        XDocument xdoc = XDocument.Load(xmlReader);
        xmlReader.Close();
        return xdoc;
    }

    public string GetWebResponseAsString(HttpWebResponse response)
    {
        Encoding enc = System.Text.Encoding.GetEncoding(1252);
        StreamReader loResponseStream = new
        StreamReader(response.GetResponseStream(), enc);
        return loResponseStream.ReadToEnd();
    }
}
jejernig
  • 319
  • 2
  • 8
  • Although this sample class wasn't the best, your fork works like a charm! I started with a RestSharp implementation but just couldn't get it to work after a couple days banging on it... THANK YOU – HAL9000 Mar 22 '12 at 03:51
  • Being a total beginner with this oauth stuff, it would be helpful to get some sample code for how to use this. My starting point is what I downloaded from the forked GitHub repo, and the etrade-supplied oath_consumer_key and consumer_secret ... BTW, in the GitHub code, the parameters for IOAuthSession.GetUserAuthorizationUrlForToken are reversed, compared to what the rest of the code seems to expect. – Jimmy Apr 15 '12 at 13:00
  • I figured it out, and added an answer. – Jimmy Apr 15 '12 at 14:35
  • Just posted first-blush C# client using the jejernig's DevDefined branch: [https://github.com/bmsapp/sappworks.stocks.public](https://github.com/bmsapp/sappworks.stocks.public) – HAL9000 Jun 11 '12 at 03:53
  • 1
    Are there ways to automate this so that user does not have to interact with browser at all for authentication? It would be nice if user can simply set the username and password, and be done with it. – Antony Oct 03 '13 at 04:27
  • Same question as above me - do you have an implementation that also does the username/password form submission via code so no human/browser intervention is needed? – Code Monkey Jul 30 '15 at 02:38
  • Hmm, I am getting a nonce_used error when trying to renew a token using https://github.com/Investars/DevDefined.OAuth.Etrade. I just use the nonce created by the code, so I assume that is should not have been used before. The access token was received using E*TRADE's php sdk. What could I be doing wrong? – Jimmy Aug 20 '15 at 16:02
7

Here's the code I've used to connect to the ETrade API (tested and works).

One caveat: You need to implement your own storage of user tokens. I've not included that here since the code I created is highly domain specific.

First, I added DotNetOpenAuth to the project and created an ETradeConsumer (it derives from DotNetOpenAuth's WebConsumer):

EtradeConsumer.cs

public static class ETradeConsumer
{
    public static string AccessUrl 
    { 
        get 
        { 
            return "https://etws.etrade.com/oauth/access_token"; 
        } 
    }

    public static string RequestUrl 
    { 
        get 
        { 
            return "https://etws.etrade.com/oauth/request_token"; 
        } 
    }

public static string UserAuthorizedUrl 
    {  
        get 
        { 
            return "https://us.etrade.com/e/t/etws/authorize"; 
        } 
    }
private static readonly ServiceProviderDescription ServiceProviderDescription = new ServiceProviderDescription()
{
    AccessTokenEndpoint = new MessageReceivingEndpoint(AccessUrl, HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
    ProtocolVersion = ProtocolVersion.V10a,
    RequestTokenEndpoint = new MessageReceivingEndpoint(RequestUrl, HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
    TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
    UserAuthorizationEndpoint = new MessageReceivingEndpoint(new Uri(UserAuthorizedUrl), HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest)
};

public static DesktopConsumer CreateConsumer(IConsumerTokenManager tokenManager)
{
    return new DesktopConsumer(ServiceProviderDescription, tokenManager);
}

public static Uri PrepareRequestAuthorization(DesktopConsumer consumer, out string requestToken)
{
    if (consumer == null)
    {
        throw new ArgumentNullException("consumer");
    }

        Uri authorizationUrl = consumer.RequestUserAuthorization(null, null, out requestToken);

    authorizationUrl = new Uri(string.Format("{0}?key={1}&token={2}", ServiceProviderDescription.UserAuthorizationEndpoint.Location.AbsoluteUri, consumer.TokenManager.ConsumerKey, requestToken));
    return authorizationUrl;
}

public static AuthorizedTokenResponse CompleteAuthorization(DesktopConsumer consumer, string requestToken, string userCode)
    {
    var customServiceDescription = new ServiceProviderDescription
    {
            RequestTokenEndpoint = ServiceProviderDescription.RequestTokenEndpoint,
        UserAuthorizationEndpoint =
                new MessageReceivingEndpoint(
                string.Format("{0}?key={1}&token={2}",  ServiceProviderDescription.UserAuthorizationEndpoint.Location.AbsoluteUri,
                              consumer.TokenManager.ConsumerKey, requestToken),
                HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest),
        AccessTokenEndpoint = new MessageReceivingEndpoint(
        ServiceProviderDescription.AccessTokenEndpoint.Location.AbsoluteUri + "?oauth_verifier" + userCode + string.Empty,
                HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest),
        TamperProtectionElements = ServiceProviderDescription.TamperProtectionElements,
        ProtocolVersion = ProtocolVersion.V10a
    };

    var customConsumer = new DesktopConsumer(customServiceDescription, consumer.TokenManager);
    var response = customConsumer.ProcessUserAuthorization(requestToken, userCode);
    return response;
    }

}

Secondly, you need to create a class to manage Etrade tokens. As an example, I created the following class. It manages the tokens through an InMemoryCollection, but it really should be held somewhere else (a database, or a cookie, or something so that the user doesn't have to authenticate/authorize every single time). The ConsumerKey and ConsumerSecret tokens are things you sign up for through Etrade:

public class ETradeTokenManager : IConsumerTokenManager 
{
    private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();
public string ConsumerKey { get { return "YourConsumerKey"; } }
public string ConsumerSecret { get { return "YourConsumerSecret";  } }
    
public string GetTokenSecret(string token)
{
    return tokensAndSecrets[token];
}

public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response)
{
    tokensAndSecrets[response.Token] = response.TokenSecret;
}

public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret)
{
    tokensAndSecrets.Remove(requestToken);
    tokensAndSecrets[accessToken] = accessTokenSecret;
}

public TokenType GetTokenType(string token)
{
    throw new NotImplementedException();
}
}

Finally, put the following in (I used ASP.NET MVC 3. Your framework may differ):

public ActionResult EtradeAuthorize(string returnUrl)
   {
        var consumer = ETradeConsumer.CreateConsumer(TokenManager);
    string requestToken;
    Uri popupWindow = ETradeConsumer.PrepareRequestAuthorization(consumer, out requestToken);
    var etradeViewModel = new ETradeAuthorizeViewModel(popupWindow, requestToken);
    return View(etradeViewModel);
    }

    [HttpPost]
    public ActionResult CompleteAuthorization(FormCollection formCollection)
    {
    string accessToken = "";
    var consumer = ETradeConsumer.CreateConsumer(TokenManager);
    var authorizationReponse = ETradeConsumer.CompleteAuthorization(consumer, formCollection["requestToken"], formCollection["userCode"]);
    if (authorizationReponse != null)
    {
        accessToken = authorizationReponse.AccessToken;
    }
    var etradeViewModel = new ETradeCompleteAuthorizeViewModel(formCollection["requestToken"], formCollection["userCode"], accessToken);
    return View(etradeViewModel);
    }

If you get a 400 Bad Request, take out the callbackUrl for Etrade. For some reason it throws a bad request whenever a callback URL is used. They prefer oob (Out of Band). In order to use oob, set null to the Callback URL in the Consumer.Channel.Send() method.

There are other issues. This issue: Due to a logon delay or other issue, your authentication could not be completed at this time. Please try again. is caused by the authorize portion of the call not being processed properly. Specifically, Etrade requires that the authorize URL looks as follows:

https://us.etrade.com/e/t/etws/authorize?key={yourConsumerKey}&token={requestToken}

The OAuth specification requires that the request token should be request_token={requestToken} and not token={requestToken}.

I couldn't get the Etrade API to authorize correctly with the WebConsumer but once I switched to the Desktop Consumer and manipulated the request myself, it worked correctly.

Community
  • 1
  • 1
George Stocker
  • 57,289
  • 29
  • 176
  • 237
  • 1
    Due to a logon delay or other issue, your authentication could not be completed at this time. Please try again. error) --- I had this issue and it took a lot of time to resolve. Look at the tokens you get back when you requested an unauthorized token. Make sure you use the entire thing! In ETrade, the token usually has encoded characters, whereas most other oauth implementations dont. so you cant use [A-za-z] regular expression to parse it. Also, you need to use _urlencoded(token)_ in your ***BASESTRING*** when building your signature. – Authman Apatira Nov 18 '11 at 16:21
  • @AuthmanApatira I ran into the same issue, and you are correct, the token had to be URL encoded to be sent across in a querystring. – George Stocker Dec 22 '11 at 16:18
  • Glad my response could be of some help to someone =). It took me two weeks to figure that one out – Authman Apatira Dec 26 '11 at 08:35
  • The E*TRADE API documentation says to use https://us.etrade.com/e/etws/authorize but they are wrong. It's https://us.etrade.com/e/t/etws/authorize. Thanks – TinyTimZamboni Oct 24 '13 at 23:49
  • @GeorgeStocker, I am getting in PHP SDK 'Due to a logon delay or other issue, your authentication could not be completed at this time. Please try again. ' You said : the request token should be request_token={requestToken} and not token={requestToken}. Should I change the `token` part in the url : https://us.etrade.com/e/t/etws/authorize?key={yourConsumerKey}&token={requestToken} to `request_token` in this way : https://us.etrade.com/e/t/etws/authorize?key={yourConsumerKey}&request_token={requestToken} ? That too did not work for me. – Istiaque Ahmed Jan 13 '14 at 15:11
  • @IstiaqueAhmed It's possible they've changed the API again. This answer was relevant back in 2011, but I'm unsure of the current state of their API. It's also possible you're having another issue. I haven't read their API docs in 2 years, I hope they've improved them since then. – George Stocker Jan 13 '14 at 16:15
  • @IstiaqueAhmed Etrade deviates from the spec (at least they did in 2011) in that their service looked for `token`; that's what your URL should have for them. It wasn't clear in my answer that I was pointing out that they deviated from the spec. – George Stocker Jan 13 '14 at 16:16
  • hey, @IstiaqueAhmed. Did you got the solution? I'm facing same error in PHP-SDK :( please help me – Ravi Dhoriya ツ Jan 18 '14 at 12:22
  • Does the etrade API support refresh_token? The docs that I see at https://content.etrade.com/etrade/estation/pdf/API_Technical_Documentation.pdf are from 2012, don't mention anything about refresh, and seem to only support oauth 1.0 (and I believe refresh_token is oauth 2), so the odds point to a negative answer, but I know of one other broker where one has to request permission to use refresh_token, so I thought I'd ask... – Jimmy Jul 23 '15 at 13:55
  • @Jimmy That's a good question for the etrade people; I haven't worked with this API since 2012, so I can't tell you an answer. – George Stocker Jul 23 '15 at 14:47
  • Hi, I tried to use the above code. Can you please let me know what is ETradeAuthorizeViewModel in EtradeAuthorize(string returnUrl) action? and what is TokenManager in ETradeConsumer.CreateConsumer(TokenManager); – Novice Programmer Aug 19 '15 at 12:47
  • 1
    @NoviceProgrammer It's possible that code was changed between the two; that `TokenManager` is actually `ETradeTokenManager`; I'm not at all sure any more, it's been years since I had access to this code; the `ETradeAuthorizeViewModel` IIRC is just a key value bag of the output from the Etrade calls (or input, I can never remember). – George Stocker Aug 19 '15 at 14:07
3

If you get "Due to a logon delay or other issue, your authentication could not be completed at this time. Please try again.",

then I think you put wrong keys in the authorize url.

You should follow the document by replacing the appropriate keys in this format

https://us.etrade.com/e/etws/authorize?key=&token=

Minh Do
  • 329
  • 3
  • 7
  • I ran into this issue using RestSharp to try to connect. I think it has to do with improper Url encoding of tokens. I am going to give the devdevined etrade fork a try. – HAL9000 Mar 21 '12 at 23:28
1

To use the example class + GitHub's code from jejernig's answer, I used the following:

TokenBase token = new TokenBase { ConsumerKey = "oauth_consumer_key from ETRADE" }; // OAuthRepository only seems to use the consumer key
OAuthRepository rep = new OAuthRepository(token, "consumer_secret from ETRADE");
OAuthSession session = rep.CreateSession();
IToken accessToken = rep.GetAccessToken(session);

I just removed the abstract from BaseOAuthRepository, and had to fix the GitHub code because IOAuthSession.GetUserAuthorizationUrlForToken()'s parameters were reversed (I changed the rest of the code to match the interface's parameters).

I am getting the dreaded Due to a logon delay or other issue, your authentication could not be completed at this time. Please try again. message, but this may be due to an actual logon issue that I have to resolve.

Community
  • 1
  • 1
Jimmy
  • 5,131
  • 9
  • 55
  • 81
-2

Clear cookies and try again. I am not sure, why this happens. But, once you get this error, unless otherwise you clear the cookies, you will get the same error. I am able to login successfully and call some of the REST services.

Mathew
  • 7