Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Twitter Handler OAuth Signing code is incorrect #1695

@speige

Description

@speige

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions