OAuth is one of those things where I always wonder about how bad the state of libraries etc are. It seems like a problem pretty much everyone will tackle, but I am still not able to find a simple, library that works well with .NET Core HttpClient without being a whole framework in itself or at least very framework dependent. Recently I needed just that for OAuth 1.0a, but ended up rolling my own. It is just the very basics, but I wanted to put it out there, because I guess others might have use for it, or I will some time in the future.
public class OAuth1
{
private static string validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private static Random random = new Random();
public string SignatureBase(string method, string url, Dictionary<string, string> oauthParams)
{
return method + "&" + Uri.EscapeDataString(url) + "&" + Uri.EscapeDataString(string.Join("&", oauthParams.OrderBy(p => p.Key).Select(p => $"{p.Key}={p.Value}")));
}
public string Signature(string consumerSecret, string tokenSecret, string signatureBase)
{
var encoding = new ASCIIEncoding();
string key = Uri.EscapeDataString(consumerSecret) + "&" + (string.IsNullOrEmpty(tokenSecret) ? "" : Uri.EscapeDataString(tokenSecret));
byte[] keyBytes = encoding.GetBytes(key);
byte[] messageBytes = encoding.GetBytes(signatureBase);
using (HMACSHA1 SHA1 = new HMACSHA1(keyBytes))
{
var hashed = SHA1.ComputeHash(messageBytes);
return Convert.ToBase64String(hashed);
}
}
public string OAuthHeader(Dictionary<string, string> oauthParams)
{
return "OAuth " + string.Join(", ", oauthParams.Select(p => $@"{p.Key}=""{p.Value}"""));
}
public string GenerateTimeStamp()
{
TimeSpan ts = DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, 0));
return ((int)Math.Floor(ts.TotalSeconds)).ToString();
}
public string Nonce(int length = 32)
{
var nonceString = new StringBuilder();
for (int i = 0; i < length; i++)
{
nonceString.Append(validChars[random.Next(0, validChars.Length - 1)]);
}
return nonceString.ToString();
}
public async Task<string> Request(string url, IDictionary<string, string> otherParams, string oAuthHeader)
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Authorization", oAuthHeader);
string fullUrl = otherParams.Any() ? (url + "?" + string.Join("&", otherParams.Select(p => $"{p.Key}={p.Value}"))) : url;
HttpResponseMessage httpResp = await httpClient.GetAsync(fullUrl);
return await httpResp.Content.ReadAsStringAsync();
}
}
}
With this in place we can do requests with the correct headers like this.
string _consumerKey = "aaa";
string _consumerSecret = "bbb";
string _accessToken = "ccc";
string _accessTokenSecret = "ddd";
var oAuth = new OAuth1();
string url = "http://myfancysite.com/api/cars/register";
string timestamp = oAuth.GenerateTimeStamp();
string nonce = oAuth.Nonce();
Dictionary<string, string> oAuthParams = new Dictionary<string, string>()
{
{ "oauth_consumer_key", _consumerKey },
{ "oauth_nonce", nonce },
{ "oauth_signature_method", "HMAC-SHA1" },
{ "oauth_timestamp", timestamp.ToString()},
{ "oauth_token", _accessToken },
{ "oauth_version", "1.0"},
};
Dictionary<string, string> otherParams = new Dictionary<string, string>()
{
{ "email", "[email protected]" },
{ "make", "Skoda" },
{ "model", "Fabia" },
{ "firstname", "Johnny" },
{ "lastname", "Madsen" }
};
string signatureBase = oAuth.SignatureBase("GET", url, oAuthParams.Concat(otherParams.Select(p => new KeyValuePair<string, string>(p.Key, Uri.EscapeDataString(p.Value)))).ToDictionary(p => p.Key, p => p.Value));
string signature = oAuth.Signature(_consumerSecret, _accessTokenSecret, signatureBase);
oAuthParams.Add("oauth_signature", signature);
string oAuthHeader = oAuth.OAuthHeader(oAuthParams);
var result = await oAuth.Request(url, otherParams, oAuthHeader);