Encontré su pregunta cuando estaba buscando una solución a un problema similar. Lo resolví haciendo 2 nuevas clases, que puedes leer en este coderwall post.
También voy a copiar y pegar el post completo aquí:
DotNetOpenAuth.AspNet 401 Error no autorizado y persistente Token de acceso secreto fijo las
Al diseñar QuietThyme, nuestro Gerente de la nube de libros electrónicos, sabíamos que todo el mundo odia la creación de nuevas cuentas tanto como nosotros. Comenzamos a buscar las bibliotecas OAuth y OpenId que pudiéramos aprovechar para permitir el inicio de sesión social. Terminamos usando la biblioteca DotNetOpenAuth.AspNet
para la autenticación de usuarios, porque es compatible con Microsoft, Twitter, Facebook, LinkedIn y Yahoo, y muchos otros desde el principio. Si bien tuvimos algunos problemas al configurarlo todo, al final solo necesitábamos hacer algunas personalizaciones pequeñas para que funcionase en su mayor parte (descrito en un previous coderwall post).Notamos que, a diferencia de todos los demás, el cliente de LinkedIn no se autenticaba y devolvía un error 401 no autorizado de DotNetOpenAuth. Rápidamente se hizo evidente que esto se debía a un problema de firma y, después de consultar la fuente, pudimos determinar que el secreto de AccessToken recuperado no se está utilizando con la solicitud de información de perfil autenticada.
Realmente tiene sentido, la razón por la cual la clase OAuthClient no incluye el secreto del token de acceso recuperado es que normalmente no es necesario para fines de autenticación, que es el propósito principal de la biblioteca ASP.NET OAuth.
Necesitamos realizar solicitudes autenticadas contra la API, después de que el usuario haya iniciado sesión, para recuperar cierta información de perfil estándar, incluida la dirección de correo electrónico y el nombre completo. Pudimos resolver este problema haciendo uso de un InMemoryOAuthTokenManager de forma temporal.
public class LinkedInCustomClient : OAuthClient
{
private static XDocument LoadXDocumentFromStream(Stream stream)
{
var settings = new XmlReaderSettings
{
MaxCharactersInDocument = 65536L
};
return XDocument.Load(XmlReader.Create(stream, settings));
}
/// Describes the OAuth service provider endpoints for LinkedIn.
private static readonly ServiceProviderDescription LinkedInServiceDescription =
new ServiceProviderDescription
{
AccessTokenEndpoint =
new MessageReceivingEndpoint("https://api.linkedin.com/uas/oauth/accessToken",
HttpDeliveryMethods.PostRequest),
RequestTokenEndpoint =
new MessageReceivingEndpoint("https://api.linkedin.com/uas/oauth/requestToken?scope=r_basicprofile+r_emailaddress",
HttpDeliveryMethods.PostRequest),
UserAuthorizationEndpoint =
new MessageReceivingEndpoint("https://www.linkedin.com/uas/oauth/authorize",
HttpDeliveryMethods.PostRequest),
TamperProtectionElements =
new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
//ProtocolVersion = ProtocolVersion.V10a
};
private string ConsumerKey { get; set; }
private string ConsumerSecret { get; set; }
public LinkedInCustomClient(string consumerKey, string consumerSecret)
: this(consumerKey, consumerSecret, new AuthenticationOnlyCookieOAuthTokenManager()) { }
public LinkedInCustomClient(string consumerKey, string consumerSecret, IOAuthTokenManager tokenManager)
: base("linkedIn", LinkedInServiceDescription, new SimpleConsumerTokenManager(consumerKey, consumerSecret, tokenManager))
{
ConsumerKey = consumerKey;
ConsumerSecret = consumerSecret;
}
//public LinkedInCustomClient(string consumerKey, string consumerSecret) :
// base("linkedIn", LinkedInServiceDescription, consumerKey, consumerSecret) { }
/// Check if authentication succeeded after user is redirected back from the service provider.
/// The response token returned from service provider authentication result.
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "We don't care if the request fails.")]
protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
{
// See here for Field Selectors API http://developer.linkedin.com/docs/DOC-1014
const string profileRequestUrl =
"https://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,industry,summary,email-address)";
string accessToken = response.AccessToken;
var profileEndpoint =
new MessageReceivingEndpoint(profileRequestUrl, HttpDeliveryMethods.GetRequest);
try
{
InMemoryOAuthTokenManager imoatm = new InMemoryOAuthTokenManager(ConsumerKey, ConsumerSecret);
imoatm.ExpireRequestTokenAndStoreNewAccessToken(String.Empty, String.Empty, accessToken, (response as ITokenSecretContainingMessage).TokenSecret);
WebConsumer w = new WebConsumer(LinkedInServiceDescription, imoatm);
HttpWebRequest request = w.PrepareAuthorizedRequest(profileEndpoint, accessToken);
using (WebResponse profileResponse = request.GetResponse())
{
using (Stream responseStream = profileResponse.GetResponseStream())
{
XDocument document = LoadXDocumentFromStream(responseStream);
string userId = document.Root.Element("id").Value;
string firstName = document.Root.Element("first-name").Value;
string lastName = document.Root.Element("last-name").Value;
string userName = firstName + " " + lastName;
string email = String.Empty;
try
{
email = document.Root.Element("email-address").Value;
}
catch(Exception)
{
}
var extraData = new Dictionary<string, string>();
extraData.Add("accesstoken", accessToken);
extraData.Add("name", userName);
extraData.AddDataIfNotEmpty(document, "headline");
extraData.AddDataIfNotEmpty(document, "summary");
extraData.AddDataIfNotEmpty(document, "industry");
if(!String.IsNullOrEmpty(email))
{
extraData.Add("email",email);
}
return new AuthenticationResult(
isSuccessful: true, provider: this.ProviderName, providerUserId: userId, userName: userName, extraData: extraData);
}
}
}
catch (Exception exception)
{
return new AuthenticationResult(exception);
}
}
}
Aquí está la sección que ha cambiado desde el cliente base de LinkedIn escrito por Microsoft.
InMemoryOAuthTokenManager imoatm = new InMemoryOAuthTokenManager(ConsumerKey, ConsumerSecret);
imoatm.ExpireRequestTokenAndStoreNewAccessToken(String.Empty, String.Empty, accessToken, (response as ITokenSecretContainingMessage).TokenSecret);
WebConsumer w = new WebConsumer(LinkedInServiceDescription, imoatm);
HttpWebRequest request = w.PrepareAuthorizedRequest(profileEndpoint, accessToken);
Por desgracia, el método IOAuthTOkenManger.ReplaceRequestTokenWithAccessToken(..)
no se ejecuta hasta después de las VerifyAuthentication()
devuelve el método, así que en vez tenga que crear una nueva y TokenManager y crear un WebConsumer
y HttpWebRequest
utilizando las credenciales accessToken que acabamos de recuperar.
Esto resuelve nuestro problema simple 401 no autorizado.
Ahora, ¿qué ocurre si desea conservar las credenciales de AccessToken después del proceso de autenticación? Esto podría ser útil para un cliente DropBox, por ejemplo, donde le gustaría sincronizar archivos a DropBox de un usuario de forma asíncrona. El problema se remonta a la forma en que se escribió la biblioteca AspNet, se asumió que DotNetOpenAuth solo se usaría para la autenticación automática del usuario, no como base para otras llamadas api de OAuth. Afortunadamente la solución fue bastante simple, todo lo que tuve que hacer fue modificar la base AuthetnicationOnlyCookieOAuthTokenManger
para que el método ReplaceRequestTokenWithAccessToken(..)
almacenara la nueva clave y secretos de AccessToken.
/// <summary>
/// Stores OAuth tokens in the current request's cookie
/// </summary>
public class PersistentCookieOAuthTokenManagerCustom : AuthenticationOnlyCookieOAuthTokenManager
{
/// <summary>
/// Key used for token cookie
/// </summary>
private const string TokenCookieKey = "OAuthTokenSecret";
/// <summary>
/// Primary request context.
/// </summary>
private readonly HttpContextBase primaryContext;
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationOnlyCookieOAuthTokenManager"/> class.
/// </summary>
public PersistentCookieOAuthTokenManagerCustom() : base()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationOnlyCookieOAuthTokenManager"/> class.
/// </summary>
/// <param name="context">The current request context.</param>
public PersistentCookieOAuthTokenManagerCustom(HttpContextBase context) : base(context)
{
this.primaryContext = context;
}
/// <summary>
/// Gets the effective HttpContext object to use.
/// </summary>
private HttpContextBase Context
{
get
{
return this.primaryContext ?? new HttpContextWrapper(HttpContext.Current);
}
}
/// <summary>
/// Replaces the request token with access token.
/// </summary>
/// <param name="requestToken">The request token.</param>
/// <param name="accessToken">The access token.</param>
/// <param name="accessTokenSecret">The access token secret.</param>
public new void ReplaceRequestTokenWithAccessToken(string requestToken, string accessToken, string accessTokenSecret)
{
//remove old requestToken Cookie
//var cookie = new HttpCookie(TokenCookieKey)
//{
// Value = string.Empty,
// Expires = DateTime.UtcNow.AddDays(-5)
//};
//this.Context.Response.Cookies.Set(cookie);
//Add new AccessToken + secret Cookie
StoreRequestToken(accessToken, accessTokenSecret);
}
}
Luego de usar este PersistentCookieOAuthTokenManager
todo lo que tiene que hacer es modificar el constructor DropboxClient, o cualquier otro cliente en la que desea que persista la accessToken Secreto
public DropBoxCustomClient(string consumerKey, string consumerSecret)
: this(consumerKey, consumerSecret, new PersistentCookieOAuthTokenManager()) { }
public DropBoxCustomClient(string consumerKey, string consumerSecret, IOAuthTokenManager tokenManager)
: base("dropBox", DropBoxServiceDescription, new SimpleConsumerTokenManager(consumerKey, consumerSecret, tokenManager))
{}
Sé que hay una manera más agradable para dar formato al código, pero no puedo por la vida de mí encontrar. Al hacer clic en el botón de código en la pregunta no pareció funcionar. Si alguien quiere aconsejar sobre cómo solucionarlo, muy apreciado. –
El formato de código ahora está basado en etiquetas, y no tenía ninguna etiqueta específica de idioma en su publicación, por lo que no hizo nada. Añadí arriba de tu código para forzarlo a resaltarlo. Consulte http://meta.stackexchange.com/a/128910/190311 –