OAuth com verificação em .NET

103

Estou tentando criar um aplicativo cliente baseado em .NET (em WPF - embora, por enquanto, esteja apenas fazendo isso como um aplicativo de console) para integrar com um aplicativo habilitado para OAuth, especificamente Mendeley ( http: // dev .mendeley.com ), que aparentemente usa OAuth de três pernas.

Esta é a primeira vez que uso OAuth e estou tendo muita dificuldade para começar a usá-lo. Encontrei várias bibliotecas ou auxiliares .NET OAuth, mas eles parecem ser mais complicados do que eu acho que preciso. Tudo o que quero fazer é emitir solicitações REST para a API Mendeley e obter respostas de volta!

Até agora, tentei:

O primeiro (DotNetOpenAuth) parece que poderia fazer o que eu precisava se eu gastasse horas e horas tentando descobrir como. O segundo e o terceiro, pelo que posso dizer, não suportam os códigos de verificação que Mendeley está enviando de volta - embora eu possa estar errado sobre isso :)

Consegui uma chave e segredo do consumidor de Mendeley e, com o DotNetOpenAuth, consegui lançar um navegador com a página do Mendeley fornecendo um código de verificação para o usuário entrar no aplicativo. No entanto, nesse ponto eu me perdi e não consegui descobrir como fornecer isso de volta ao aplicativo de maneira sensata.

Estou muito disposto a admitir que não tenho ideia por onde começar com isso (embora pareça que haja uma curva de aprendizado bastante íngreme) - se alguém puder me apontar na direção certa, eu agradeceria!

John
fonte

Respostas:

182

Eu concordo com você. As classes de suporte OAuth de código aberto disponíveis para aplicativos .NET são difíceis de entender, excessivamente complicadas (quantos métodos são expostos pelo DotNetOpenAuth?), Mal projetadas (veja os métodos com 10 parâmetros de string no módulo OAuthBase.cs desse google link que você forneceu - não há gerenciamento de estado), ou de outra forma insatisfatório.

Não precisa ser tão complicado.

Não sou um especialista em OAuth, mas produzi uma classe de gerenciador do lado do cliente OAuth, que uso com sucesso com Twitter e TwitPic. É relativamente simples de usar. É código aberto e está disponível aqui: Oauth.cs

Para análise, no OAuth 1.0a ... meio engraçado, há um nome especial e parece um "padrão", mas até onde eu sei o único serviço que implementa "OAuth 1.0a" é o Twitter. Eu acho que isso é padrão o suficiente . ok, de qualquer maneira, no OAuth 1.0a, a maneira como funciona para aplicativos de desktop é esta:

  1. Você, o desenvolvedor do aplicativo, registra o aplicativo e obtém uma "chave do consumidor" e um "segredo do consumidor". Na Arstechnica, há uma análise bem escrita de por que esse modelo não é o melhor , mas como dizem, é o que é .

  2. Seu aplicativo é executado. Na primeira vez em que é executado, ele precisa fazer com que o usuário conceda explicitamente a aprovação para que o aplicativo faça solicitações REST autenticadas oauth para o Twitter e seus serviços irmãos (como TwitPic). Para fazer isso, você deve passar por um processo de aprovação, envolvendo a aprovação explícita do usuário. Isso acontece apenas na primeira vez que o aplicativo é executado. Como isso:

    • solicitar um "token de solicitação". Token temporário conhecido.
    • pop uma página da web, passando esse token de solicitação como um parâmetro de consulta. Esta página da web apresenta a IU ao usuário, perguntando "deseja conceder acesso a este aplicativo?"
    • o usuário efetua login na página da web do Twitter e concede ou nega o acesso.
    • a página html de resposta aparece. Se o usuário concedeu acesso, há um PIN exibido em uma fonte de 48 pontos
    • o usuário agora precisa cortar / colar aquele alfinete em uma caixa de formulário do Windows e clicar em "Avançar" ou algo semelhante.
    • o aplicativo de desktop então faz uma solicitação autenticada oauth para um "token de acesso". Outra solicitação REST.
    • o aplicativo de desktop recebe o "token de acesso" e o "segredo de acesso".

Após a dança da aprovação, o aplicativo de desktop pode apenas usar o "token de acesso" e o "segredo de acesso" específicos do usuário (junto com a "chave do consumidor" e o "segredo do consumidor" específicos do aplicativo) para fazer solicitações autenticadas em nome do usuário para o Twitter. Eles não expiram, embora se o usuário desautorizar o aplicativo, ou se o Twitter por algum motivo desautorizar seu aplicativo, ou se você perder seu token de acesso e / ou segredo, você precisará fazer a dança de aprovação novamente .


Se você não for inteligente, o fluxo de IU pode espelhar o fluxo de mensagens OAuth de várias etapas. Há um caminho melhor.

Use um controle WebBrowser e abra a página da web de autorização no aplicativo de desktop. Quando o usuário clicar em "Permitir", pegue o texto de resposta desse controle WebBrowser, extraia o PIN automaticamente e obtenha os tokens de acesso. Você envia 5 ou 6 solicitações HTTP, mas o usuário precisa ver apenas uma única caixa de diálogo Permitir / Negar. Simples.

Como isso:
texto alternativo


Se você classificou a IU, o único desafio que resta é produzir solicitações assinadas oauth. Isso confunde muitas pessoas porque os requisitos de assinatura oauth são meio particulares. É isso que a classe simplificada do OAuth Manager faz.

Código de exemplo para solicitar um 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");

É ISSO . Simples. Como você pode ver no código, a maneira de chegar aos parâmetros oauth é por meio de um indexador baseado em string, algo como um dicionário. O método AcquireRequestToken envia uma solicitação assinada oauth para a URL do serviço que concede tokens de solicitação, também conhecidos como tokens temporários. Para o Twitter, este URL é " A classe do gerenciador OAuth faz isso para você automaticamente. Ela gera nonces e carimbos de data / hora e versões e assinaturas automaticamente - seu aplicativo não precisa se preocupar ou estar ciente dessas coisas. Basta definir os valores do parâmetro oauth e faça uma chamada de método simples. a classe de gerenciador envia a solicitação e analisa a resposta para você. https://api.twitter.com/oauth/request_token ". A especificação oauth diz que você precisa empacotar o conjunto de parâmetros oauth (token, token_secret, nonce, timestamp, consumer_key, version e callback), de uma certa maneira (url-codificado e unido por e comercial), e em um lexicograficamente- ordenada, gere uma assinatura nesse resultado e, em seguida, empacote esses mesmos parâmetros junto com a assinatura, armazenados no novo parâmetro oauth_signature, de uma maneira diferente (unidos por vírgulas).

Ok, então o quê? Depois de obter o token de solicitação, você abre a IU do navegador da web, na qual o usuário concederá a aprovação explicitamente. Se você fizer isso direito, você o colocará em um navegador integrado. Para o Twitter, a URL é " https://api.twitter.com/oauth/authorize?oauth_token= " com o oauth_token anexado. Faça isso em código como:

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

(Se você estivesse fazendo isso em um navegador externo, você usaria System.Diagnostics.Process.Start(url).)

Definir a propriedade Url faz com que o controle WebBrowser navegue para essa página automaticamente.

Quando o usuário clica no botão "Permitir", uma nova página é carregada. É um formulário HTML e funciona da mesma forma que em um navegador completo. Em seu código, registre um manipulador para o evento DocumentedCompleted do controle WebBrowser e, nesse manipulador, pegue o pino:

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();

Isso é um pouco de raspagem de tela HTML.

Depois de pegar o pino, você não precisa mais do navegador da web, então:

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

... e você pode querer chamar Dispose () nele também.

A próxima etapa é obter o token de acesso, enviando outra mensagem HTTP junto com esse pin. Esta é outra chamada oauth assinada, construída com a ordenação e formatação oauth que descrevi acima. Mas, mais uma vez, isso é realmente simples com a classe OAuth.Manager:

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

Para o Twitter, esse URL é " https://api.twitter.com/oauth/access_token ".

Agora você tem tokens de acesso e pode usá-los em solicitações HTTP assinadas. Como isso:

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

... onde urlestá o endpoint do recurso. Para atualizar o status do usuário, seria " http://api.twitter.com/1/statuses/update.xml?status=Hello ".

Em seguida, defina essa string no cabeçalho HTTP denominado Authorization .

Para interagir com serviços de terceiros, como TwitPic, você precisa construir um cabeçalho OAuth ligeiramente diferente , como este:

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

Para o Twitter, os valores para o url e o reino dos creds de verificação são " https://api.twitter.com/1/account/verify_credentials.json " e " http://api.twitter.com/ " respectivamente.

... e coloque essa string de autorização em um cabeçalho HTTP chamado X-Verify-Credentials-Authorization . Em seguida, envie isso para o seu serviço, como TwitPic, junto com qualquer solicitação que você está enviando.

É isso aí.

Juntos, o código para atualizar o status do Twitter pode ser algo assim:

// 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 é meio complicado nos bastidores, mas usá-lo não precisa ser. O OAuth.Manager lida com a geração de solicitações oauth de saída e o recebimento e processamento de conteúdo oauth nas respostas. Quando a solicitação Request_token fornece um oauth_token, seu aplicativo não precisa armazená-lo. O Oauth.Manager é inteligente o suficiente para fazer isso automaticamente. Da mesma forma, quando a solicitação access_token recebe de volta um token de acesso e segredo, você não precisa armazená-los explicitamente. O OAuth.Manager trata desse estado para você.

Nas execuções subsequentes, quando você já tiver o token de acesso e o segredo, poderá instanciar o OAuth.Manager assim:

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;

... e então gere cabeçalhos de autorização como acima.

// 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);
}

Você pode baixar uma DLL contendo a classe OAuth.Manager aqui . Há também um arquivo de ajuda nesse download. Ou você pode ver o arquivo de ajuda online .

Veja um exemplo de Windows Form que usa este gerenciador aqui .


EXEMPLO DE TRABALHO

Baixe um exemplo funcional de uma ferramenta de linha de comando que usa a classe e a técnica descritas aqui:

Cheeso
fonte
Olá, muito obrigado pela sua resposta! Na verdade, mudei do OAuth (desisti do Mendeley e optei por uma alternativa) - mas li sua resposta e ela fez muito sentido e é muito abrangente. Também marquei a classe que você escreveu para qualquer momento futuro em que eu precise! Muito obrigado novamente.
João
2
Olá, Cheeso, obrigado por compartilhar seu código e sua explicação detalhada. Você forneceu uma solução excelente, mas simples. No entanto, você desejará fazer uma pequena alteração em seu método GetSignatureBase para oferecer suporte a soluções não "oob". Para não "oob", você precisa codificar o URL do retorno de chamada, então você vai querer adicionar algo assim quando estiver iterando por this._params: if (p1.Key == "callback") {p.Add ( "oauth_" + p1.Key, UrlEncode (p1.Value)); continue;}
Johnny Oshika
1
Isso não funciona para OAuth 2.0. Esta classe é para OAuth 1.0a. OAuth2.0 é significativamente mais simples de usar, pois não há assinatura e classificação lexicográfica dos vários parâmetros. Portanto, você provavelmente não precisa de uma classe externa para fazer OAuth 2.0, ou ... se você precisa de uma classe externa, será muito mais simples do que esta.
Cheeso
1
arquivo de ajuda
Kiquenet
3
Todos os links parecem estar quebrados. Encontrei uma cópia aqui: gist.github.com/DeskSupport/2951522#file-oauth-cs
John