-
Notifications
You must be signed in to change notification settings - Fork 586
Twitter Handler OAuth Signing code is incorrect #1695
Description
I'm not aware of a specific bug caused by this. My guess is it work 99% of the time with the current api calls, but if it were used for any other twitter api calls, they'd all fail.
If you read the documentation here:
https://developer.twitter.com/en/docs/basics/authentication/guides/creating-a-signature
you'll see that the code here doesn't match:
https://github.com/aspnet/Security/blob/dev/src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs
One example is: All uses of UrlEncoder.Encode should be replaced with Uri.EscapeDataString
There are several others. I found this because I copied the code in order to call other twitter apis & they failed until I made corrections.
In TwitterHandler.cs all the methods have a slightly different copy/pasted OAuth code. I've extracted it to a generic method. I've tested & it is working on several different twitter apis, I haven't tested it on all of them, so it's possible there's still an issue somewhere (for example, I expect all data to be in the query params, but some apis may have a body instead).
The existing requests in TwitterHandler would need to be refactored to use this generic method. I haven't coded that since I'm using the built-in version for authentication & my version for extra api calls.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Twitter;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Authentication.Twitter
{
public class TwitterHandler
{
protected string ComputeSignature(string consumerSecret, string tokenSecret, string signatureData)
{
using (HMACSHA1 algorithm = new HMACSHA1())
{
algorithm.Key = Encoding.ASCII.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}&{1}", Uri.EscapeDataString(consumerSecret), string.IsNullOrEmpty(tokenSecret) ? string.Empty : Uri.EscapeDataString(tokenSecret)));
return Convert.ToBase64String(algorithm.ComputeHash(Encoding.ASCII.GetBytes(signatureData)));
}
}
protected async Task<JObject> ExecuteRequestAsync(AccessToken accessToken, ClaimsIdentity identity, string url, HttpMethod httpMethod, Dictionary<string, string> queryParameters = null)
{
StringBuilder canonicalizedRequestBuilder = new StringBuilder();
canonicalizedRequestBuilder.Append(httpMethod.Method);
canonicalizedRequestBuilder.Append("&");
canonicalizedRequestBuilder.Append(Uri.EscapeDataString(url));
SortedDictionary<string, string> authorizationParts = new SortedDictionary<string, string>()
{
{ "oauth_consumer_key", Options.ConsumerKey },
{ "oauth_nonce", Guid.NewGuid().ToString("N") },
{ "oauth_signature_method", "HMAC-SHA1" },
{ "oauth_timestamp", GenerateTimeStamp() },
{ "oauth_token", accessToken.Token },
{ "oauth_version", "1.0" }
};
ImmutableSortedDictionary<string, string> signatureParts = (queryParameters ?? new Dictionary<string, string>()).Union(authorizationParts).ToImmutableSortedDictionary(x => x.Key, x => x.Value);
string parameterString = signatureParts.Select(x => string.Format("{0}={1}", Uri.EscapeDataString(x.Key), Uri.EscapeDataString(x.Value))).Aggregate((x, y) => string.Format("{0}&{1}", x, y));
canonicalizedRequestBuilder.Append("&");
canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString));
string signature = ComputeSignature(Options.ConsumerSecret, accessToken.TokenSecret, canonicalizedRequestBuilder.ToString());
authorizationParts.Add("oauth_signature", signature);
string queryString = (queryParameters != null && queryParameters.Any() ? "?" + queryParameters.Select(x => x.Key + "=" + x.Value).Aggregate((x, y) => x + "&" + y) : "");
HttpRequestMessage request = new HttpRequestMessage(httpMethod, url + queryString);
request.Headers.Add("Authorization", "OAuth " + authorizationParts.Select(x => string.Format("{0}=\"{1}\"", x.Key, Uri.EscapeDataString(x.Value))).Aggregate((x, y) => string.Format("{0},{1}", x, y)));
HttpResponseMessage response = await _client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
response.EnsureSuccessStatusCode(); // throw
}
string responseText = await response.Content.ReadAsStringAsync();
return JObject.Parse(responseText);
}
}
}