c# - ¿Cómo uso OAuth para conectarme a la API de Etrade?
.net dotnetopenauth (5)
E-Trade lanzó su API recientemente y proporcionó documentación técnica que es algo útil pero no completa .
¿Alguien tiene un ejemplo completamente funcional en C # que muestre cómo funciona esto?
He podido realizar la autenticación utilizando OAuth correctamente, pero cuando se trata de obtener información de mi cuenta o datos de mercado, los servidores fallan.
Aquí está el código que he usado para conectar con la API de ETrade (probado y funciona).
Una advertencia: debe implementar su propio almacenamiento de tokens de usuario. No lo he incluido aquí ya que el código que creé es altamente específico del dominio.
Primero, agregué DotNetOpenAuth
al proyecto y creé un ETradeConsumer
(deriva del WebConsumer de DotNetOpenAuth):
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;
}
}
En segundo lugar, debe crear una clase para administrar los tokens de Etrade. Como ejemplo, creé la siguiente clase. Administra los tokens a través de InMemoryCollection, pero en realidad debería guardarse en otro lugar (una base de datos o una cookie, o algo que el usuario no tenga que autenticar o autorizar cada vez). Los tokens ConsumerKey
y ConsumerSecret
son cosas a las que debe suscribirse a través de 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();
}
}
Finalmente, ponga lo siguiente en (utilicé ASP.NET MVC 3. Su marco puede diferir):
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);
}
Si obtiene una 400 Bad Request
, retire el callbackUrl
para Etrade. Por alguna razón, lanza una solicitud errónea cada vez que se utiliza una URL de devolución de llamada. Prefieren oob
(fuera de banda). Para utilizar oob
, establezca null
en la URL de devolución de llamada en el método Consumer.Channel.Send()
.
Hay otros temas. Este problema: Due to a logon delay or other issue, your authentication could not be completed at this time. Please try again.
Due to a logon delay or other issue, your authentication could not be completed at this time. Please try again.
es causado por la parte authorize
de la llamada que no se está procesando correctamente. Específicamente, Etrade requiere que la URL de autorización tenga el siguiente aspecto:
https://us.etrade.com/e/t/etws/authorize?key={yourConsumerKey}&token={requestToken}
La especificación OAuth requiere que el token de solicitud sea request_token={requestToken}
y no el token={requestToken}
.
No pude obtener la API de Etrade para autorizar correctamente con WebConsumer
pero una vez que WebConsumer
a Desktop Consumer
y manipulé la solicitud, funcionó correctamente.
Borrar las cookies y vuelva a intentarlo. No estoy seguro de por qué sucede esto. Pero, una vez que obtenga este error, a menos que de lo contrario borre las cookies, obtendrá el mismo error. Puedo iniciar sesión correctamente y llamar a algunos de los servicios REST.
Para usar la clase de ejemplo + código de GitHub de la respuesta de jejernig , usé lo siguiente:
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);
Acabo de eliminar el abstract
de BaseOAuthRepository
y tuve que corregir el código de GitHub porque IOAuthSession.GetUserAuthorizationUrlForToken()
se invirtieron (cambié el resto del código para que coincida con los parámetros de la interfaz).
Estoy recibiendo el temido Due to a logon delay or other issue, your authentication could not be completed at this time. Please try again.
Due to a logon delay or other issue, your authentication could not be completed at this time. Please try again.
mensaje, pero esto puede deberse a un problema de inicio de sesión real que tengo que resolver.
Pude conectarme utilizando la biblioteca de DevDefined OAuth, pero tuve que hacer algunos ajustes en la fuente para que funcionara correctamente. Bifurqué el repositorio para que pueda descargar el src que usé y construirle un archivo .dll.
Repo: GitHub
Ejemplo de clase:
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();
}
}
Si aparece "Debido a un retraso en el inicio de sesión u otro problema, su autenticación no se pudo completar en este momento. Inténtelo de nuevo".
Entonces creo que pones claves incorrectas en la url de autorización.
Debe seguir el documento reemplazando las claves apropiadas en este formato