Cómo enviar peticiones utilizando OAuth: Firmando peticiones

En el mes de Mayo intenté mostrar cómo podíamos recuperar los tokens de autenticación, tanto para Twitter como para Yahoo, con el objetivo de hacer uso del protocolo abierto OAuth. A pesar de todo, es posible que aún tengamos dudas sobre cómo realizar nuestras peticiones y nos hagan falta algunos tips a tener en cuenta 🙂 En este post me centraré en Twitter.

En el apartado de documentación podemos ver una barra lateral derecha donde se nos muestran todas las acciones disponibles, la URL a la cual debemos realizar la petición y el HTTP Verb necesario:

 

Para este ejemplo, voy a mostrar cómo sería para el caso más común que es actualizar
el estado
.

En primer lugar, voy a crear una aplicación de consola donde vamos a llamar a
un wrapper creado para interactuar con la API y que a su vez utilizará la clase
implementada para el protocolo OAuth en el
post anterior
.

using System;
using TwitterWrapper;

namespace TwitterClient
{
    class Program
    {
        static void Main()
        {
            const string status = "Testing my twitter client with #OAuth and ¡special characteres! ¿*?";

           var result = new Twitter().UpdateStatus(status);

            Console.WriteLine("The status was successfully updated!");
            Console.WriteLine("ID: {0}", result.Id);
            Console.WriteLine("Date: {0}", result.Created);
            Console.WriteLine("Status: {0}", result.Text);

            Console.ReadLine();
        }
    }
}

Como podéis ver, he creado una clase llamada Twitter desde la cual llamo al método UpdateStatus. El objetivo del mismo es reunir los parámetros necesarios para poder construir la petición y que posteriormente sea firmada.

using System.IO;
using System.Net;
using System.Web;
using System.Xml.Serialization;
using OAuthTools;
using TwitterWrapper.Resources;

namespace TwitterWrapper
{
    public class Twitter
    {
        private const string ConsumerKey = "yourConsumerKey";
        private const string ConsumerSecret = "yourConsumerSecret";
        private const string AccessToken = "yourAccessToken";
        private const string AccessTokenSecret = "yourAccessTokenSecret";

        public Status UpdateStatus(string status, WebProxy webProxy = null)
        {
            var query = string.Format("http://api.twitter.com/1/statuses/update.xml?status={0}", HttpUtility.UrlEncode(status));
            var request = new OAuth(ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret);
            var result = request.PostResource(query);

            return Serialize(result);
        }

        public Status Serialize(string xml)
        {
            var xmlSerializer = new XmlSerializer(typeof(Status));
            var reader = new StringReader(xml);
            var status = (Status)xmlSerializer.Deserialize(reader);

            return status;
        }
    }
}

Una vez que la petición ha sido realizada, intentamos almacenar el resultado
XML dentro de la clase Status gracias a XmlSerializer y los XmlAttributes en los atributos que
necesitemos almacenar en nuestra aplicación.

Tip: Es muy importante hacer uso de
HttpUtility.UrlEncode para recuperar los valores Unicode del
status que acabamos de pasarle al método. De lo contrario algunos caracteres especiales podrían interpretarse
de un modo erróneo y serán omitidos de la petición
. Por ejemplo, uno de
los casos que me estaba ocurriendo, es al intentar introducir un hashtag. Al no
pasar esta información en formato Unicode, si intentamos transformar el string
en Uri interpretará el símbolo # y todo lo que venía detrás como un ancla ;)

Si bajamos una capa más en el código, llegamos a la clase OAuth pero con un
nuevo método para las peticiones POST llamado PostResource.

/* POST Requests */
public string PostResource(string url)
{
    var uri = new Uri(url);
    var parameters = GetOAuthParameters(HttpMethod.Post, url, HttpUtility.ParseQueryString(uri.Query), _accessToken, _accessTokenSecret);
    url = string.Format("{0}://{1}{2}", uri.Scheme, uri.Authority, uri.AbsolutePath);
    var oAuthRequest = new OAuthRequest(parameters, url, HttpMethod.Post, proxy: _proxy);
    var queryParameters = HttpUtility.ParseQueryString(uri.Query);
    var response = oAuthRequest.GetResponse(queryParameters);

    return response;
}

En esta función vamos a formatear el parámetro de entrada (url de Twitter +
 el nuevo estado) dentro de la clase Uri y posteriormente vamos a recuperar los
parámetros para el protocolo OAuth que irán incluidos en la cabecera.

Una vez que tenemos generados estos parámetros (nonce, timestamp, la firma,
etc.) podemos hacer la llamada a través del método POST utilizando la clase
oAuthRequest. Crearemos una instancia de dicha clase con los
parámetros recopilados hasta el momento y por último llamaremos a GetResponse
para obtener el resultado.

using System;
using System.Text;
using System.Collections.Specialized;
using System.Net;
using System.IO;

namespace OAuthTools
{
    public class OAuthRequest
    {
        private readonly HttpWebRequest _request;
        private static byte[] _content;

        public string GetResponse(NameValueCollection queryParameters = null)
        {
            try
            {
                /*POST method*/
                if (queryParameters != null)
                {
                    var sb = new StringBuilder();
                    for (var i = 0; i < queryParameters.AllKeys.Length; i++)
                    {
                        var key = queryParameters.AllKeys[i];

                        sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(queryParameters[key]));

                        if (i < queryParameters.Count - 1)
                            sb.Append("&");

                    }

                    _content = Encoding.ASCII.GetBytes(sb.ToString());
                    _request.ContentLength = _content.Length;

                    using (var stream = _request.GetRequestStream())
                    {
                        if (_request.ContentLength > 0)
                            stream.Write(_content, 0, _content.Length);
                    }

                }

                using (var response = _request.GetResponse())
                {
                    var stream = response.GetResponseStream();
                    using (var reader = new StreamReader(stream))
                    {
                        return reader.ReadToEnd();
                    }
                }
            }
            catch (WebException ex)
            {
                //If something is wrong, We'll retrieve the server response.
                /*    HTTP 400 Bad Request
                        o Unsupported parameter
                        o Unsupported signature method
                        o Missing required parameter
                        o Duplicated OAuth Protocol Parameter
                      HTTP 401 Unauthorized
                        o Invalid Consumer Key
                        o Invalid / expired Token
                        o Invalid signature
                        o Invalid / used nonce
                */
                if (ex.Response is HttpWebResponse)
                {
                    using (var reader = new StreamReader(ex.Response.GetResponseStream()))
                    {
                        var result = reader.ReadToEnd();
                        return result;
                    }
                }

                throw;
            }
        }

        //  2. For each parameter, the name is immediately followed by an ‘=’ character (ASCII code 61), a ‘”’ character (ASCII code 34), the parameter value (MAY be empty), and another ‘”’ character (ASCII code 34).
        //  3. Parameters are separated by a comma character (ASCII code 44) and OPTIONAL linear whitespace.
        //  4. The OPTIONAL realm parameter is added
        /// <summary>
        /// The OAuth Protocol Parameters are sent in the Authorization  header the following way:
        /// 1.  Parameter names and values are encoded.
        /// </summary>
        /// <param name="parameters">oAuthParameters</param>
        /// <param name="url"></param>
        /// <param name="method">Http Method</param>
        /// <param name="proxy"></param>
        public OAuthRequest(NameValueCollection parameters, string url, string method, IWebProxy proxy = null)
        {
            ServicePointManager.Expect100Continue = false;
            ServicePointManager.UseNagleAlgorithm = false;

            var header = new StringBuilder();

            header.Append("OAuth ");

            for (var i = 0; i < parameters.Count; i++)
            {
                var key = parameters.GetKey(i);
                header.Append(string.Format("{0}="{1}"", key, parameters[key]));

                if (i < parameters.Count - 1)
                    header.Append(",");
            }

            _request = CreateHttpWebRequest(url, method, proxy);

            _request.Headers["Authorization"] = header.ToString();

        }

        private static HttpWebRequest CreateHttpWebRequest(string url, string method, IWebProxy proxy = null)
        {
            var httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
            httpWebRequest.Method = method.ToUpper();

            if (proxy != null) httpWebRequest.Proxy = proxy;

            if (method == HttpMethod.Post)
                httpWebRequest.ContentType = "application/x-www-form-urlencoded";

            return httpWebRequest;
        }
    }
}

Con esta clase podríamos realizar tanto peticiones GET como POST, donde en
las segundas se envía el cuerpo del mensaje.

Para poder ver el ejemplo completo, he actualizado el proyecto de CodePlex.

Espero que sea de utilidad :D

¡Saludos!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *