C# Coinbase Pro API How to Trouble Shoot Responses. 401 - c#

I keep Getting 401 responses from coinbase pro and I have double checked my passphrase and api key and they are correct. Is there any other reason I would get Unauth response
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString("F0", CultureInfo.InvariantCulture);
var method = "GET";
var requestPath = "/accounts";
var body = "";
var data = timestamp + method + requestPath + body;
var secret = "***";
var signedMessage = GetHMACInHex(secret, data);
var client = new HttpClient()
{
BaseAddress = new Uri("https://api.pro.coinbase.com")
};
client.DefaultRequestHeaders.Add("CB-ACCESS-SIGN", signedMessage);
client.DefaultRequestHeaders.Add("CB-ACCESS-KEY", "***");
client.DefaultRequestHeaders.Add("CB-ACCESS-TIMESTAMP", timestamp);
client.DefaultRequestHeaders.Add("CB-ACCESS-PASSPHRASE", "***");
client.DefaultRequestHeaders.Add("User-Agent", "CoinbaseProClient");
This is how I Sign the Data
static string GetHMACInHex(string key, string data)
{
var decoded = Convert.FromBase64String(key);
var dataBytes = Encoding.UTF8.GetBytes(data);
using (var hmac = new HMACSHA256(decoded))
{
var hash = hmac.ComputeHash(dataBytes);
return Convert.ToBase64String(hash);
}
}

401 status code is returned by Coinbase when following validation of the resource fails on POST or PUT requests:
authentication_error- Invalid auth (generic)
invalid_token- Invalid Oauth token
revoked_token- Revoked Oauth
token
expired_token- Expired Oauth token
If your problem is not listed here, you may miss to pass DefaultRequestHeaders.
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));

Related

Create hmacHash with stream result differs for requests with formatted json

I want to regenerate a token to verify the request using hmacHash
The resource documentation is in javascript and I have to implement it in C#.
here what I found in their documentation on generating the token.
routes.post("/webhook", (request) => {
const body = request.rawBody;
const signatureTime = "2022-05-17T09:47:51.935Z";
const signingKey = "localyStoredKey";
const hmac = createHmac("sha256", signingKey);
hmac.update(`${signatureTime}${body}`);
const token = hmac.digest("hex");
}
here is what I have done in C#
public async Task<string> GetWebhookAuthenticationTokenAsync(Stream requestBody) // requestBody is from HttpContext.Request.Body
{
var signingKey = "localyStoredKey"; // should be from db
var signatureTime = "2022-05-17T09:47:51.935Z"; // a timestamp
var reader = new StreamReader(requestBody, Encoding.UTF8);
var body = await reader.ReadToEndAsync();
var byteArray1 = Encoding.UTF8.GetBytes($"{signatureTime}");
var byteArray2 = Encoding.UTF8.GetBytes(body);
var concatenatedByteArray = byteArray1.Concat(byteArray2).ToArray();
var stringInMemoryStream = new MemoryStream(concatenatedByteArray);
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(signingKey));
var hashBytes = hmac.ComputeHash(stringInMemoryStream);
var token = BitConverter.ToString(hashBytes).Replace("-", String.Empty).ToLower();
return token;
}
This works fine when I send the request body(json) without newline characters(\n).
when I send the formatted json body with the request, the token is different from javascript generated token. What am I missing?
simply if json is {"pro": "duct"} the tokens are matched but when json {\n "pro": "duct"\n} its different.
PS: I found the issue is due to Carriage Return \r in json body. Any help to handle this independently from the platform?

How to generate JWT Bearer Flow OAuth access tokens from a .net core client?

I'm having trouble getting my .NET Core client to generate OAuth access tokens for a salesforce endpoint that requires OAuth of type 'JWT Bearer Flow'.
It seems there are limited .NET Framework examples that show a .NET client doing this, however none that show a .NET Core client doing it
e.g.
https://salesforce.stackexchange.com/questions/53662/oauth-jwt-token-bearer-flow-returns-invalid-client-credentials
So in my .NET Core 3.1 app i've generated a self signed certificate, added the private key to the above example's code when loading in the certificate, however a System.InvalidCastExceptionexception exception occurs on this line:
var rsa = certificate.GetRSAPrivateKey() as RSACryptoServiceProvider;
Exception:
System.InvalidCastException: 'Unable to cast object of type 'System.Security.Cryptography.RSACng' to type 'System.Security.Cryptography.RSACryptoServiceProvider'.'
It appears that this private key is used in the JWT Bearer Flow as part of the signature, and perhaps RSACryptoServiceProvider is not used in .NET core as it was in .NET Framework.
My question is this - is there actually a way in .NET Core to generate access tokens for the OAuth JWT Bearer Flow?
Full code that I'm using:
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var token = GetAccessToken();
}
static dynamic GetAccessToken()
{
// get the certificate
var certificate = new X509Certificate2(#"C:\temp\cert.pfx");
// create a header
var header = new { alg = "RS256" };
// create a claimset
var expiryDate = GetExpiryDate();
var claimset = new
{
iss = "xxxxxx",
prn = "xxxxxx",
aud = "https://test.salesforce.com",
exp = expiryDate
};
// encoded header
var headerSerialized = JsonConvert.SerializeObject(header);
var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
var headerEncoded = ToBase64UrlString(headerBytes);
// encoded claimset
var claimsetSerialized = JsonConvert.SerializeObject(claimset);
var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
var claimsetEncoded = ToBase64UrlString(claimsetBytes);
// input
var input = headerEncoded + "." + claimsetEncoded;
var inputBytes = Encoding.UTF8.GetBytes(input);
// signature
var rsa = (RSACryptoServiceProvider) certificate.GetRSAPrivateKey();
var cspParam = new CspParameters
{
KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
};
var aescsp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false };
var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
var signatureEncoded = ToBase64UrlString(signatureBytes);
// jwt
var jwt = headerEncoded + "." + claimsetEncoded + "." + signatureEncoded;
var client = new WebClient();
client.Encoding = Encoding.UTF8;
var uri = "https://login.salesforce.com/services/oauth2/token";
var content = new NameValueCollection();
content["assertion"] = jwt;
content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
string response = Encoding.UTF8.GetString(client.UploadValues(uri, "POST", content));
var result = JsonConvert.DeserializeObject<dynamic>(response);
return result;
}
static int GetExpiryDate()
{
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var currentUtcTime = DateTime.UtcNow;
var exp = (int)currentUtcTime.AddMinutes(4).Subtract(utc0).TotalSeconds;
return exp;
}
static string ToBase64UrlString(byte[] input)
{
return Convert.ToBase64String(input).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}
Well - it turns out posting to stackoverflow gets the brain cogs turning.
The answer ended up being doing a deep dive to find a similar issue here and using the solution from x509certificate2 sign for jwt in .net core 2.1
I ended up replacing the following code:
var cspParam = new CspParameters
{
KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
};
var aescsp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false };
var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
var signatureEncoded = ToBase64UrlString(signatureBytes);
With this code which makes use of the System.IdentityModel.Tokens.Jwt nuget package:
var signingCredentials = new X509SigningCredentials(certificate, "RS256");
var signature = JwtTokenUtilities.CreateEncodedSignature(input, signingCredentials);
Full code after solution:
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var token = GetAccessToken();
}
static dynamic GetAccessToken()
{
// get the certificate
var certificate = new X509Certificate2(#"C:\temp\cert.pfx");
// create a header
var header = new { alg = "RS256" };
// create a claimset
var expiryDate = GetExpiryDate();
var claimset = new
{
iss = "xxxxx",
prn = "xxxxx",
aud = "https://test.salesforce.com",
exp = expiryDate
};
// encoded header
var headerSerialized = JsonConvert.SerializeObject(header);
var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
var headerEncoded = ToBase64UrlString(headerBytes);
// encoded claimset
var claimsetSerialized = JsonConvert.SerializeObject(claimset);
var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
var claimsetEncoded = ToBase64UrlString(claimsetBytes);
// input
var input = headerEncoded + "." + claimsetEncoded;
var inputBytes = Encoding.UTF8.GetBytes(input);
var signingCredentials = new X509SigningCredentials(certificate, "RS256");
var signature = JwtTokenUtilities.CreateEncodedSignature(input, signingCredentials);
// jwt
var jwt = headerEncoded + "." + claimsetEncoded + "." + signature;
var client = new WebClient();
client.Encoding = Encoding.UTF8;
var uri = "https://test.salesforce.com/services/oauth2/token";
var content = new NameValueCollection();
content["assertion"] = jwt;
content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
string response = Encoding.UTF8.GetString(client.UploadValues(uri, "POST", content));
var result = JsonConvert.DeserializeObject<dynamic>(response);
return result;
}
static int GetExpiryDate()
{
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var currentUtcTime = DateTime.UtcNow;
var exp = (int)currentUtcTime.AddMinutes(4).Subtract(utc0).TotalSeconds;
return exp;
}
static string ToBase64UrlString(byte[] input)
{
return Convert.ToBase64String(input).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}
I am replying to this question just because such a similar answer would have helped me a lot when I landed on this page the first time.
First of all you don't have to generate the JWT from the C# client.
To generate a JWT token you can use this website: https://jwt.io/
There is a very well done video showing how to generate a JWT token:
https://www.youtube.com/watch?v=cViU2-xVscA&t=1680s
Once generated, use it from your C# client to call the get access_token endpoint
https://developer.salesforce.com/docs/atlas.en-us.api_iot.meta/api_iot/qs_auth_access_token.htm
(Watch the video on YT)
If all is correct you will get the access_token
To run the API calls, all you need is the access_token and not the JWT.
Once you have it add it to the HTTP calls like this
public static void AddBearerToken(this HttpRequestMessage request, string accessToken)
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
}
From time to time the access_token will expire. To check its validity you can call the token introspect api
https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oidc_token_introspection_endpoint.htm&type=5
You need to pass two additional parameters: client_id and client_secret
The client_id is the Consumer Key. You get it from the Connected App in Salesforce
The client_server is the Consumer Secret. You get it from Connected App in Salesforce
If the introspect token API returns a response with
{ active: false, ... }
it means that the access_token is expired and you need to issue a new one.
To issue a new access_token simply call the "/services/oauth2/token" again using the same JWT.

Replicate System.Net.Http method in RestSharp

I'm trying to replicate an example for calling a web API using RestSharp and I'm running into some issues.
Here is the example
string apiKey = "my key";
string apiSecret = "my secret";
string requestUri = "https://www.cryptopia.co.nz/Api/GetBalance";
var postData = new
{
Currency = "DOT"
};
// Create Request
var request = new HttpRequestMessage();
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(requestUri);
request.Content = new ObjectContent(typeof(object), postData, new JsonMediaTypeFormatter());
// Authentication
string requestContentBase64String = string.Empty;
if (request.Content != null)
{
// Hash content to ensure message integrity
using (var md5 = MD5.Create())
{
requestContentBase64String = Convert.ToBase64String(md5.ComputeHash(await request.Content.ReadAsByteArrayAsync()));
}
}
//create random nonce for each request
var nonce = Guid.NewGuid().ToString("N");
//Creating the raw signature string
var signature = Encoding.UTF8.GetBytes(string.Concat(apiKey, HttpMethod.Post, HttpUtility.UrlEncode(request.RequestUri.AbsoluteUri.ToLower()), nonce, requestContentBase64String));
using (var hmac = new HMACSHA256(Convert.FromBase64String(apiSecret)))
{
request.Headers.Authorization = new AuthenticationHeaderValue("amx", string.Format("{0}:{1}:{2}", apiKey, Convert.ToBase64String(hmac.ComputeHash(signature)), nonce));
}
// Send Request
using (var client = new HttpClient())
{
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
Console.WriteLine(await response.Content.ReadAsStringAsync());
//{"Success":true,"Error":null,"Data":[{"CurrencyId":2,"Symbol":"DOT","Total":9646.07411016,"Available":9646.07411016,"Unconfirmed":0.0,"HeldForTrades":0.0,"PendingWithdraw":0.0,"Address":"1HEfio1kreDBgj5uCw4VHbEDSgc6YJXfTN","Status":"OK","StatusMessage":null}]}
}
}
I believe the issue I'm having is that the signature variable isn't being created properly. I'm setting the body of the RestSharp request using
request.AddJsonBody(request.JsonSerializer.Serialize(new { Currency = "DOT" }));
To get the body request.content I'm using
var body = request.Parameters.Where(p => p.Type == ParameterType.RequestBody).FirstOrDefault().Value.ToString();
To get the requestContentBase64String I'm using
var requestSignatureBase64String = Convert.ToBase64String(signatureBytes);
And finally to add the Authorization header I'm using
request.AddHeader("Authorization", $"amx {apiKey}:{requestSignatureBase64String}:{nonce}");
The response from the server is
"Success":false,"Error":"Signature does not match request parameters."

Not getting access_token by calling https://login.microsoftonline.com/{tenant}/oauth2/token

I want to get user email from user id (object identifier) from web api, but getting blank response while calling api for token. I am running this code from my Web API. Please help. Below is the code.
Given full permission to APIs
Getting Blank response in below line.
var responseBytes = await webClient.UploadValuesTaskAsync(url, "POST", requestParameters);
Below is code
var tenant = "tenant ID";
var clientID = "app ID";
// I've tried graph.microsoft.com and graph.microsoft.com/.default
var resource = "https://graph.microsoft.com";
var secret = "client secret";
string token;
using (var webClient = new WebClient())
{
var requestParameters = new NameValueCollection();
requestParameters.Add("scope", resource);
requestParameters.Add("client_id", clientID);
requestParameters.Add("grant_type", "client_credentials");
requestParameters.Add("client_secret", secret);
var url = "https://login.microsoftonline.com/{tenant}/oauth2/token";
webClient.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
var responseBytes = await webClient.UploadValuesTaskAsync(url, "POST", requestParameters);
var responseBody = Encoding.UTF8.GetString(responseBytes);
var jsonObject = Newtonsoft.Json.JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(responseBody);
token = jsonObject.Value<string>("access_token");
}
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetAsync(new Uri("https://graph.microsoft.com/v1.0/user/" + ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")));
Your error is here:
requestParameters.Add("scope", resource);
It needs to be resource rather than scope:
requestParameters.Add("resource", resource);
Can you help me understand what documentation or tutorial you followed to make this mistake? I have seen it happen before and I am trying to understand the patterns here.
The documentation and authentication flow you should be following is here.

C# Twitter request OAuth Token

I am trying to request token from the Twitter API based on my consumer key and consumer secret key. However I am getting a The remote server returned an error: (403) Forbidden which I am not sure why?
This is my attempt so far
//Get Request Token
string oauth_consumer_key = "<consumer key>";
string oauth_consumer_secret = "<consumer secret>";
Uri requestToken = new Uri("https://api.twitter.com/oauth2/token?oauth_consumer_key=" + oauth_consumer_key + "&oauth_consumer_secret=" + oauth_consumer_secret);
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requestToken);
req.Method = "POST";
try
{
using (var response = req.GetResponse() as HttpWebResponse)
if (req.HaveResponse && response != null)
{
}
}
catch (WebException wex)
{
}
The code is incomplete however running through it I always seem to get a Forbidden exception?
If I post the URL request as follows, it works fine and returns the token
https://twitter.com/oauth/request_token?oauth_consumer_key=bidjtABOkF0b3mvw1UaHWDf7x&oauth_consumer_secret=qWO208QapZvckBoyWu3QET8uFnBXXlG3tSTWSS8oAOtoY8qwHD
Am I doing something wrong?
Solved my problem by using Task / Asyc and also adding authorization OAuth headers. Now able to get access token
Here is my solution:
public async Task<ActionResult> AccessToken()
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.twitter.com/oauth2/token");
string oauth_consumer_key = "<consumer key>";
string oauth_consumer_secret = "<consumer secret>";
string url = "https://api.twitter.com/oauth2/token?oauth_consumer_key=" + oauth_consumer_key + "&oauth_consumer_secret=" + oauth_consumer_secret;
var customerInfo = Convert.ToBase64String(new UTF8Encoding()
.GetBytes(oauth_consumer_key + ":" + oauth_consumer_secret));
// Add authorization to headers
request.Headers.Add("Authorization", "Basic " + customerInfo);
request.Content = new StringContent("grant_type=client_credentials", Encoding.UTF8,
"application/x-www-form-urlencoded");
HttpResponseMessage response = await httpClient.SendAsync(request);
string json = await response.Content.ReadAsStringAsync();
var serializer = new JavaScriptSerializer();
dynamic item = serializer.Deserialize<object>(json);
ViewBag.access_token = item["access_token"];
return View();
}

Categories

Resources