OAuth con verificación en .NET

103

Estoy tratando de crear una aplicación cliente basada en .NET (en WPF, aunque por el momento solo lo estoy haciendo como una aplicación de consola) para integrarla con una aplicación habilitada para OAuth, específicamente Mendeley ( http: // dev .mendeley.com ), que aparentemente usa OAuth de 3 patas.

Esta es la primera vez que uso OAuth y estoy teniendo muchas dificultades para empezar a utilizarlo. Encontré varias bibliotecas o ayudantes de .NET OAuth, pero parecen ser más complicadas de lo que creo que necesito. ¡Todo lo que quiero hacer es poder emitir solicitudes REST a la API de Mendeley y obtener respuestas!

Hasta ahora he intentado:

La primera (DotNetOpenAuth) parece que podría hacer lo que necesitaba si pasara horas y horas tratando de averiguar cómo. El segundo y el tercero, lo mejor que puedo decir, no son compatibles con los códigos de verificación que Mendeley está enviando, aunque podría estar equivocado al respecto :)

Tengo una clave de consumidor y un secreto de Mendeley, y con DotNetOpenAuth logré lanzar un navegador con la página de Mendeley que proporciona un código de verificación para que el usuario ingrese a la aplicación. Sin embargo, en este punto me perdí y no pude encontrar la manera de devolver eso a la aplicación.

Estoy muy dispuesto a admitir que no tengo ni idea de por dónde empezar con esto (aunque parece que hay una curva de aprendizaje bastante empinada). Si alguien puede indicarme la dirección correcta, ¡lo agradecería!

Juan
fuente

Respuestas:

182

Estoy de acuerdo contigo. Las clases de soporte de OAuth de código abierto disponibles para las aplicaciones .NET son difíciles de entender, demasiado complicadas (¿cuántos métodos expone DotNetOpenAuth?), Mal diseñadas (mire los métodos con 10 parámetros de cadena en el módulo OAuthBase.cs de ese google enlace que proporcionó (no hay administración estatal en absoluto), o de otra manera insatisfactoria.

No tiene por qué ser tan complicado.

No soy un experto en OAuth, pero he producido una clase de administrador del lado del cliente de OAuth, que uso con éxito con Twitter y TwitPic. Es relativamente sencillo de usar. Es de código abierto y está disponible aquí: Oauth.cs

Para revisar, en OAuth 1.0a ... un poco gracioso, hay un nombre especial y parece un "estándar", pero hasta donde yo sé, el único servicio que implementa "OAuth 1.0a" es Twitter. Supongo que eso es lo suficientemente estándar . ok, de todos modos en OAuth 1.0a, la forma en que funciona para las aplicaciones de escritorio es la siguiente:

  1. Usted, el desarrollador de la aplicación, la registra y obtiene una "clave de consumidor" y un "secreto de consumidor". En Arstechnica, hay un análisis bien escrito de por qué este modelo no es el mejor , pero como dicen, es lo que es .

  2. Tu aplicación se ejecuta. La primera vez que se ejecuta, debe hacer que el usuario otorgue explícitamente la aprobación para que la aplicación realice solicitudes REST autenticadas por Oauth a Twitter y sus servicios hermanos (como TwitPic). Para hacer esto, debe pasar por un proceso de aprobación, que implica la aprobación explícita del usuario. Esto sucede solo la primera vez que se ejecuta la aplicación. Me gusta esto:

    • solicitar un "token de solicitud". Aka ficha temporal.
    • abre una página web, pasando ese token de solicitud como un parámetro de consulta. Esta página web presenta la interfaz de usuario al usuario y le pregunta "¿desea otorgar acceso a esta aplicación?"
    • el usuario inicia sesión en la página web de Twitter y otorga o deniega el acceso.
    • aparece la página HTML de respuesta. Si el usuario ha concedido acceso, se muestra un PIN en una fuente de 48 puntos
    • el usuario ahora necesita cortar / pegar ese pin en un cuadro de formulario de Windows y hacer clic en "Siguiente" o algo similar.
    • la aplicación de escritorio realiza una solicitud autenticada oauth de un "token de acceso". Otra solicitud de REST.
    • la aplicación de escritorio recibe el "token de acceso" y el "secreto de acceso".

Después del baile de aprobación, la aplicación de escritorio puede usar el "token de acceso" y el "secreto de acceso" específicos del usuario (junto con la "clave del consumidor" y el "secreto del consumidor" específicos de la aplicación) para realizar solicitudes autenticadas en nombre del usuario. a Twitter. Estos no caducan, aunque si el usuario desautoriza la aplicación, o si Twitter por algún motivo desautoriza tu aplicación, o si pierdes tu token de acceso y / o secreto, deberías volver a realizar el baile de aprobación. .


Si no es inteligente, el flujo de la interfaz de usuario puede reflejar el flujo de mensajes OAuth de varios pasos. Hay una mejor manera.

Utilice un control WebBrowser y abra la página web autorizada dentro de la aplicación de escritorio. Cuando el usuario hace clic en "Permitir", toma el texto de respuesta de ese control de WebBrowser, extrae el PIN automáticamente y luego obtén los tokens de acceso. Envía 5 o 6 solicitudes HTTP pero el usuario necesita ver solo un cuadro de diálogo Permitir / Denegar. Sencillo.

Me gusta esto:
texto alternativo


Si tiene la interfaz de usuario ordenada, el único desafío que queda es producir solicitudes firmadas por Oauth. Esto molesta a mucha gente porque los requisitos de firma de oauth son algo particulares. Eso es lo que hace la clase simplificada de OAuth Manager.

Código de ejemplo para solicitar un token:

var oauth = new OAuth.Manager();
// the URL to obtain a temporary "request token"
var rtUrl = "https://api.twitter.com/oauth/request_token";
oauth["consumer_key"] = MY_APP_SPECIFIC_KEY;
oauth["consumer_secret"] = MY_APP_SPECIFIC_SECRET;    
oauth.AcquireRequestToken(rtUrl, "POST");

Eso es todo . Sencillo. Como puede ver en el código, la forma de acceder a los parámetros de oauth es a través de un indexador basado en cadenas, algo así como un diccionario. El método AcquireRequestToken envía una solicitud firmada por oauth a la URL del servicio que otorga tokens de solicitud, también conocidos como tokens temporales. Para Twitter, esta URL es " https://api.twitter.com/oauth/request_token ". La especificación de oauth dice que debe empaquetar el conjunto de parámetros de oauth (token, token_secret, nonce, timestamp, consumer_key, versión y devolución de llamada), de cierta manera (codificado en URL y unido por símbolos de unión), y de forma lexicográfica- ordenado, genere una firma en ese resultado, luego empaque esos mismos parámetros junto con la firma, almacenados en el nuevo parámetro oauth_signature, de una manera diferente (unidos por comas). La clase de administrador de OAuth hace esto automáticamente. Genera nonces, marcas de tiempo, versiones y firmas automáticamente; su aplicación no necesita preocuparse o ser consciente de esas cosas. Simplemente configure los valores del parámetro oauth y realice una simple llamada al método. la clase de administrador envía la solicitud y analiza la respuesta por usted.

¿OK entonces que? Una vez que obtiene el token de solicitud, abre la interfaz de usuario del navegador web en la que el usuario otorgará explícitamente la aprobación. Si lo hace bien, lo mostrará en un navegador integrado. Para Twitter, la URL para esto es " https://api.twitter.com/oauth/authorize?oauth_token= " con el oauth_token adjunto. Haz esto en un código así:

var url = SERVICE_SPECIFIC_AUTHORIZE_URL_STUB + oauth["token"];
webBrowser1.Url = new Uri(url);

(Si estuviera haciendo esto en un navegador externo, lo usaría System.Diagnostics.Process.Start(url)).

Establecer la propiedad Url hace que el control WebBrowser navegue a esa página automáticamente.

Cuando el usuario haga clic en el botón "Permitir", se cargará una nueva página. Es un formulario HTML y funciona igual que en un navegador completo. En su código, registre un controlador para el evento DocumentedCompleted del control WebBrowser y, en ese controlador, tome el pin:

var divMarker = "<div id=\"oauth_pin\">"; // the div for twitter's oauth pin
var index = webBrowser1.DocumentText.LastIndexOf(divMarker) + divMarker.Length;
var snip = web1.DocumentText.Substring(index);
var pin = RE.Regex.Replace(snip,"(?s)[^0-9]*([0-9]+).*", "$1").Trim();

Eso es un poco de raspado de pantalla HTML.

Después de tomar el pin, ya no necesita el navegador web, así que:

webBrowser1.Visible = false; // all done with the web UI

... y es posible que desee llamar a Dispose () también.

El siguiente paso es obtener el token de acceso, enviando otro mensaje HTTP junto con ese pin. Esta es otra llamada de oauth firmada, construida con el orden y formato de oauth que describí anteriormente. Pero una vez más, esto es realmente simple con la clase OAuth.Manager:

oauth.AcquireAccessToken(URL_ACCESS_TOKEN,
                         "POST",
                         pin);

Para Twitter, esa URL es " https://api.twitter.com/oauth/access_token ".

Ahora tiene tokens de acceso y puede usarlos en solicitudes HTTP firmadas. Me gusta esto:

var authzHeader = oauth.GenerateAuthzHeader(url, "POST");

... donde urlestá el punto final del recurso. Para actualizar el estado del usuario, sería " http://api.twitter.com/1/statuses/update.xml?status=Hello ".

Luego, establezca esa cadena en el encabezado HTTP denominado Autorización .

Para interactuar con servicios de terceros, como TwitPic, debe construir un encabezado OAuth ligeramente diferente , como este:

var authzHeader = oauth.GenerateCredsHeader(URL_VERIFY_CREDS,
                                            "GET",
                                            AUTHENTICATION_REALM);

Para Twitter, los valores para la URL de verificación de créditos y el dominio son " https://api.twitter.com/1/account/verify_credentials.json " y " http://api.twitter.com/ " respectivamente.

... y coloque esa cadena de autorización en un encabezado HTTP llamado X-Verify-Credentials-Authorization . Luego envíelo a su servicio, como TwitPic, junto con cualquier solicitud que esté enviando.

Eso es.

En conjunto, el código para actualizar el estado de Twitter podría ser algo como esto:

// the URL to obtain a temporary "request token"
var rtUrl = "https://api.twitter.com/oauth/request_token";
var oauth = new OAuth.Manager();
// The consumer_{key,secret} are obtained via registration
oauth["consumer_key"] = "~~~CONSUMER_KEY~~~~";
oauth["consumer_secret"] = "~~~CONSUMER_SECRET~~~";
oauth.AcquireRequestToken(rtUrl, "POST");
var authzUrl = "https://api.twitter.com/oauth/authorize?oauth_token=" + oauth["token"];
// here, should use a WebBrowser control. 
System.Diagnostics.Process.Start(authzUrl);  // example only!
// instruct the user to type in the PIN from that browser window
var pin = "...";
var atUrl = "https://api.twitter.com/oauth/access_token";
oauth.AcquireAccessToken(atUrl, "POST", pin);

// now, update twitter status using that access token
var appUrl = "http://api.twitter.com/1/statuses/update.xml?status=Hello";
var authzHeader = oauth.GenerateAuthzHeader(appUrl, "POST");
var request = (HttpWebRequest)WebRequest.Create(appUrl);
request.Method = "POST";
request.PreAuthenticate = true;
request.AllowWriteStreamBuffering = true;
request.Headers.Add("Authorization", authzHeader);

using (var response = (HttpWebResponse)request.GetResponse())
{
    if (response.StatusCode != HttpStatusCode.OK)
        MessageBox.Show("There's been a problem trying to tweet:" +
                        Environment.NewLine +
                        response.StatusDescription);
}

OAuth 1.0a es algo complicado bajo las sábanas, pero su uso no tiene por qué serlo. OAuth.Manager maneja la generación de solicitudes de oauth salientes y la recepción y procesamiento de contenido de oauth en las respuestas. Cuando la solicitud Request_token le proporciona un oauth_token, su aplicación no necesita almacenarlo. Oauth.Manager es lo suficientemente inteligente como para hacerlo automáticamente. Del mismo modo, cuando la solicitud access_token recupera un token de acceso y un secreto, no es necesario almacenarlos explícitamente. OAuth.Manager maneja ese estado por usted.

En ejecuciones posteriores, cuando ya tenga el token de acceso y el secreto, puede crear una instancia de OAuth.Manager de esta manera:

var oauth = new OAuth.Manager();
oauth["consumer_key"] = CONSUMER_KEY;
oauth["consumer_secret"] = CONSUMER_SECRET;
oauth["token"] = your_stored_access_token;
oauth["token_secret"] = your_stored_access_secret;

... y luego generar encabezados de autorización como arriba.

// now, update twitter status using that access token
var appUrl = "http://api.twitter.com/1/statuses/update.xml?status=Hello";
var authzHeader = oauth.GenerateAuthzHeader(appUrl, "POST");
var request = (HttpWebRequest)WebRequest.Create(appUrl);
request.Method = "POST";
request.PreAuthenticate = true;
request.AllowWriteStreamBuffering = true;
request.Headers.Add("Authorization", authzHeader);

using (var response = (HttpWebResponse)request.GetResponse())
{
    if (response.StatusCode != HttpStatusCode.OK)
        MessageBox.Show("There's been a problem trying to tweet:" +
                        Environment.NewLine +
                        response.StatusDescription);
}

Puede descargar una DLL que contiene la clase OAuth.Manager aquí . También hay un archivo de ayuda en esa descarga. O puede ver el archivo de ayuda en línea .

Vea un ejemplo de un formulario de Windows que utiliza este administrador aquí .


EJEMPLO DE TRABAJO

Descargue un ejemplo funcional de una herramienta de línea de comandos que utiliza la clase y la técnica descritas aquí:

Cheeso
fuente
Hola, muchas gracias por tu respuesta! De hecho, me mudé de OAuth (me di por vencido con Mendeley y me decidí por una alternativa), pero leí tu respuesta y tenía mucho sentido y es muy completa. ¡También he marcado la clase que escribiste para cualquier momento futuro que pueda necesitar! Muchas gracias de nuevo.
John
2
Hola Cheeso, gracias por compartir tu código y tu explicación detallada. Proporcionaste una solución excelente pero simple. Sin embargo, querrá hacer un pequeño cambio en su método GetSignatureBase para admitir soluciones que no sean "oob". Para no "oob", necesita codificar la URL de la devolución de llamada, por lo que querrá agregar algo como esto cuando esté iterando a través de this._params: if (p1.Key == "callback") {p.Add ( "oauth_" + p1.Key, UrlEncode (p1.Value)); continue;}
Johnny Oshika
1
Esto no funciona para OAuth 2.0. Esta clase es para OAuth 1.0a. OAuth2.0 es significativamente más simple de usar, ya que no hay firma ni clasificación lexicográfica de los distintos parámetros. Así que probablemente no necesite una clase externa para hacer OAuth 2.0, o ... si necesita una clase externa, será mucho más simple que esta.
Cheeso
1
archivo de
ayuda en
3
Todos los enlaces parecen estar rotos. Encontré una copia aquí: gist.github.com/DeskSupport/2951522#file-oauth-cs
John