Rolling my own OAuth 1.0 client

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", "johnny@madsen.dk" },
         { "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);