I've made a call of Client.MakeRequest("/api/v3/account"); to check if I'm authorized to make such a call, but I keep getting an error status 'unauthorized' everytime I make the call.
public static void MakeRequest(string endpoint)
{
var client = new RestClient("https://api.binance.com");
long timestamp = GetTimestamp();
RestRequest request = new RestRequest(endpoint, Method.GET);
request.AddHeader("X-MBX-APIKEY", API_KEY);
request.AddQueryParameter("recvWindow", "5000");
request.AddQueryParameter("timestamp", timestamp.ToString());
request.AddQueryParameter("signature", CreateSignature(request.Parameters, API_SECRET));
RestResponse response = (RestResponse)client.Get(request);
System.Diagnostics.Debug.WriteLine(response.Content);
}
public static string CreateSignature(List<Parameter> parameters, string secret)
{
var signature = "";
if (parameters.Count > 0)
{
foreach (var item in parameters)
{
if (item.Name != "X-MBX-APIKEY")
signature += $"{item.Name}={item.Value}&";
}
signature = signature.Substring(0, signature.Length - 1);
}
byte[] keyBytes = Encoding.UTF8.GetBytes(secret);
byte[] queryStringBytes = Encoding.UTF8.GetBytes(signature);
HMACSHA256 hmacsha256 = new HMACSHA256(keyBytes);
byte[] bytes = hmacsha256.ComputeHash(queryStringBytes);
return BitConverter.ToString(bytes).Replace("-", "").ToLower();
}
private static long GetTimestamp()
{
return new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds();
}
I'm sure the signature is correct as I've followed the specifications from: https://binance-docs.github.io/apidocs/spot/en/#public-api-definitions
Other than that i've been debugging for a while and still can't quite figure out the mistake i'm making.
The endpoint is returning an unauthorized error so you're definitely hitting the endpoint. I see in the documentation that the API key can be restricted to specific IP addresses so check if the API key is correct and has no restrictions. If it does, make sure your IP configured to use the API key.
Related
I am trying to integrate an existing application with Google's OAuth2 system to make more secure calls to Google Services such as GMail and move away from the standard SMTP client sending techniques in C#. I have set up a service account and obtained a P12 key and everything is in place and the calls are going to the OAuth server but I keep getting the following JSON response back when the call to 'GetResponse' is made:
{"error":"invalid_grant","error_description":"Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe. Check your iat and exp values in the JWT claim."}
Below is the C# code I am using to make this call and I also get the same results via CURL. Every time I fiddle with "iap" and "exp" values either using local or UTC times I get different messages back. It seem that the code I have now is what I should be using but I cannot determine why I'm still getting these messages. I have made sure, according to Google Documentation, that the system clock is in sync with the Google NTP of time.google.com, but still no change. I am doing something wrong with the construction of the JSON Web Token (JWT) when making the web request call?
try
{
RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider();
List<string> jwtSegments = new List<string>();
var jwtHeader = new {
alg = "RS256",
typ = "JWT"
};
int nowTimeInEpochSeconds = (int)Math.Floor((DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds);
var jwtClaimSet = new {
iss = "***********#********.iam.gserviceaccount.com",
scope = "https://www.googleapis.com/auth/gmail.send",
aud = "https://oauth2.googleapis.com/token",
exp = nowTimeInEpochSeconds + 3600,
iat = nowTimeInEpochSeconds
};
string serializedHeader = JsonConvert.SerializeObject(jwtHeader, Formatting.None);
string serializedClaimSet = JsonConvert.SerializeObject(jwtClaimSet, Formatting.None);
byte[] jwtHeaderBytes = Encoding.UTF8.GetBytes(serializedHeader);
byte[] jwtClaimSetBytes = Encoding.UTF8.GetBytes(serializedClaimSet);
string base64EncodedHeader = Base64UrlEncode(jwtHeaderBytes);
string base64EncodedClaimSet = Base64UrlEncode(jwtClaimSetBytes);
jwtSegments.Add(base64EncodedHeader);
jwtSegments.Add(base64EncodedClaimSet);
string jwtRequestToSign = string.Join(".", jwtSegments);
byte[] jwtRequestBytesToSign = Encoding.UTF8.GetBytes(jwtRequestToSign);
X509Certificate2 cert = new X509Certificate2(#"C:\**************.p12", "notasecret");
AsymmetricAlgorithm rsaSignature = cert.PrivateKey;
byte[] signature = rsaCryptoServiceProvider.SignData(jwtRequestBytesToSign, "SHA256");
string jwt = jwtRequestToSign + "." + Base64UrlEncode(signature);
WebRequest webRequestForaccessToken = WebRequest.Create("https://oauth2.googleapis.com/token") as HttpWebRequest;
string accessTokenRequestParameters = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + jwt;
byte[] accessTokenRequestFromJwt = Encoding.UTF8.GetBytes(accessTokenRequestParameters);
string token = string.Empty;
if (webRequestForaccessToken != null)
{
webRequestForaccessToken.Method = "POST";
webRequestForaccessToken.ContentType = "application/x-www-form-urlencoded";
webRequestForaccessToken.ContentLength = accessTokenRequestFromJwt.Length;
using (Stream stream = webRequestForaccessToken.GetRequestStream())
{
stream.Write(accessTokenRequestFromJwt, 0, accessTokenRequestFromJwt.Length);
}
using (HttpWebResponse httpWebResponseForaccessToken = webRequestForaccessToken.GetResponse() as HttpWebResponse)
{
Stream streamForaccessToken = httpWebResponseForaccessToken?.GetResponseStream();
if (streamForaccessToken != null)
{
using (StreamReader streamReaderForForaccessToken = new StreamReader(streamForaccessToken))
{
string jsonUserResponseString = streamReaderForForaccessToken.ReadToEnd();
JObject groupsIoApiUserObject = JObject.Parse(jsonUserResponseString);
token = groupsIoApiUserObject["access_token"].ToString();
}
}
}
}
}
catch (WebException ex)
{
try
{
string resp = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
Console.WriteLine(resp);
}
catch (Exception parseException)
{
string m = parseException.Message;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
two reasons for that either your method of calculation is not good, or the time of your server is not correct, can check with something like:
int nowTimeInEpochSeconds = DateTimeOffset.UtcNow.AddMinutes(59).ToUnixTimeSeconds()
So as it turns out there were two things that needed correction. The first was that according the answer provided by #GAOUL, I was not calculating the epoch time correctly (though I had to modify the suggested line to remove the "AddMinutes" method to get the correct value for the "iat" field). Then, the "exp" value became the "iat" value plus 3600 (1 hour).
So my original time calculation went from this:
int nowTimeInEpochSeconds = (int)Math.Floor((DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds);
to this:
int nowTimeInEpochSeconds = (int)Math.Floor((double)DateTimeOffset.UtcNow.ToUnixTimeSeconds());
The second issue I discovered after reviewing my own code was that I was not correctly signing the signature with the private key (was not signing with private key at all).
So I replaced this branch of code:
X509Certificate2 cert = new X509Certificate2(#"C:\**************.p12", "notasecret");
AsymmetricAlgorithm rsaSignature = cert.PrivateKey;
byte[] signature = rsaCryptoServiceProvider.SignData(jwtRequestBytesToSign, "SHA256");
with this branch instead:
X509Certificate2 certificate = new X509Certificate2(#"**************.p12", "notasecret");
RSA rsa = certificate.GetRSAPrivateKey();
byte[] signature = rsa.SignData(jwtRequestBytesToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
Once I applied these changes, I got a valid response back from the Google OAuth server and it contained the access/refresh tokens along with the other fields.
Also, thanks to #dazwilkin for referencing JWT.io. That was also a big help!
I am trying to connect to an API where I need to authenticate by passing in the header a hashed string in my C# application. In the documentation from the provider though they only have an example of how to hash the string using JS functions in a react application, bellow is the screenshot of the code snippet found in said documentation.
I have already found an article here on stackoverflow on an alternative for the btoa function but I am completely stuck on the CryptoJS.HmacSHA384 function alternative. I have tried using the System.Security.Cryptography.HMACSHA384 class but can't figure out how to encode the strings I am passing to the method in order to obtain the same result, causing an Authorization denied when I try to connect to the API endpoint. Bellow is the code from the method I have written so far (that is not working):
public void myMethodToConnet(string user, string pass, string custId, string partId, string partKey, string ver, string commId)
{
int resetPasswordErrorCode;
_user = user;
_password = pass;
_rooftopId = custId;
_partnerId = partId;
_partnerKey = Encoding.UTF8.GetBytes(partKey);
_version = ver;
_communityId = commId;
_url = "url";
_token = GetToken().Result;
using (HMACSHA384 hmac = new HMACSHA384(_partnerKey))
{
_hmac = hmac.ComputeHash(Encoding.UTF8.GetBytes(_token));
_hash = BTOA($"{_user}:{Encoding.UTF8.GetString(_hmac)}:{_password}");
}
ActivateToken().Result;
}
private static string BTOA(string toEncode)
{
byte[] bytes = Encoding.GetEncoding(28591).GetBytes(toEncode);
string result = System.Convert.ToBase64String(bytes);
return result;
}
private async Task<int> ActivateToken()
{
CheckPasswordRequest request = new CheckPasswordRequest();
CheckPasswordResponse response = new CheckPasswordResponse();
StringContent content;
string jsonString;
string apiResponse;
int errorCode = 0;
using (HttpClient httpClient = new HttpClient())
{
request = new CheckPasswordRequest() { RooftopId = _rooftopId };
jsonString = JsonConvert.SerializeObject(request);
content = new StringContent(jsonString, Encoding.UTF8, "application/json");
httpClient.DefaultRequestHeaders.Add("Authorization", $"DataHub-Hash {_hash}");
using (var resp = await httpClient.PostAsync($"{_url}/CheckPassword", content))
{
apiResponse = await resp.Content.ReadAsStringAsync();
response = JsonConvert.DeserializeObject<CheckPasswordResponse>(apiResponse);
errorCode = response.ErrorCode;
}
}
return errorCode;
}
Thanks!
I finally figured it out by creating a simple HTML page where I ran the JS commands and printed out the output to than compare it to what the output in my C# application was. At the end the solution was that of converting the byte array output of the encryption algorithm to hex, to do so I created this helper method:
public static string ByteToString(byte[] input)
{
string output= "";
for (int i = 0; i < input.Length; i++)
{
output+= input[i].ToString("X2");
}
return (output);
}
by inserting the output string of this method between the user and password and running my BTOA helper method returned the string the API was expecting and managed to authenticate.
I'm trying to create a version in JIRA for a specific project.
I'm able to do the process via Postman by building my requests manually, but it fails with a 404 when creating the version record via .NET.
I'm assuming .NET adds pesky parameters to the request that Postman doesn't do.
The weird thing is that the authentication call works, but the the version creation fails.
Here's the helper I wrote:
public class JIRA
{
private string AuthToken { get; set; }
private const string c_JIRAUrl = "https://org.atlassian.net";
private const string c_LoginUrl = c_JIRAUrl + "/rest/auth/1/session";
private const string c_CreateVersionUrl = c_JIRAUrl + "/rest/api/2/version";
public JIRA()
{
//this works...
var authResponse = ExecuteRequest(c_LoginUrl, "POST", new
{
username = "login",
password = "password"
});
AuthToken = authResponse["session"]["value"].ToString();
}
public void CreateVersion(string name, string projectKey, ProjectEnvironment environment)
{
//lets hardcode the same data I use in Postman for testing purposes...
var createVersionResponse = ExecuteRequest(c_CreateVersionUrl, "POST", new
{
description = "An excellent version",
name = "1.1.2",
archived = false,
released = false,
project = "TEST"
});
}
private JObject ExecuteRequest(string url, string method, object data)
{
HttpWebResponse response;
var jsonDataString = JsonConvert.SerializeObject(data);
byte[] dataBytes = Encoding.Default.GetBytes(jsonDataString);
var responseText = string.Empty;
var wr = (HttpWebRequest)WebRequest.Create(url);
wr.ContentType = "application/json";
if (!string.IsNullOrEmpty(AuthToken))
wr.Headers.Add(HttpRequestHeader.Authorization, $"Bearer {AuthToken}");
wr.Method = method;
wr.ContentLength = dataBytes.Length;
wr.Accept = "application/json";
using (var webStream = wr.GetRequestStream())
{
webStream.Write(dataBytes, 0, dataBytes.Length);
response = (HttpWebResponse)wr.GetResponse();
}
using (var sr = new StreamReader(response.GetResponseStream()))
{
responseText = sr.ReadToEnd();
}
return JObject.Parse(responseText);
}
}
The CreateVersion method always fails with a 404.
As I've said, doing the same (retrieving the token, creating the version) all works in Postman.
Any ideas what's going on ?
Thanks.
Apparently, when retrieving the token (/rest/auth/1/session) the response contains cookies that POSTMAN was sending back in the 2nd request (creating the version). I had to fire up Fiddler to find out it was doing so because its UI was not saying so.
My .NET client was not doing so. When making it do so, it works.
I'm a little miffed that a REST service expects cookies...
I am trying to upload a file to following the API information in this service. Easy Post API.
I am able to successfully send the first GET request with Digest authentication.
I'm getting a 403 - Unauthorized when trying to upload the file with 'PUT'.
This is the code I have. I am using a custom web client to set parameters in the web request.
public class CustomWebClient : WebClient
{
private BingMailConfigOptions ConfigOptions;
public CustomWebClient(BingMailConfigOptions configOptions) : base()
{
ConfigOptions = configOptions;
}
protected override WebRequest GetWebRequest(Uri address)
{
var request = (HttpWebRequest)base.GetWebRequest(address);
request.ServicePoint.Expect100Continue = false;
request.Method = "PUT";
request.Credentials = GetCredentialCache(address, ConfigOptions);
return request;
}
public static CredentialCache GetCredentialCache(Uri uri, BingMailConfigOptions options)
{
var credentialCache = new CredentialCache
{
{
new Uri(uri.GetLeftPart(UriPartial.Authority)),
"Digest",
new NetworkCredential(options.AuthUserName, options.AuthPassword, uri.GetLeftPart(UriPartial.Authority))
}
};
return credentialCache;
}
}
// in a separate class.
private void Upload(string sessionId, string filePath)
{
_log.Trace("Trying to upload the file: " + filePath);
var file = new FileInfo(filePath);
if (file.Exists)
{
using (var uploader = new CustomWebClient(ConfigOptions))
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
Uri uri = new Uri("https://bingmail.com.au/" + "direct_upload/{0}/{1}"(sessionId, HttpUtility.UrlEncode(file.Name)));
uploader.UploadFile(uri, "PUT", filePath);
}
}
else
{
throw new Exception("File Not found");
}
}
Can you please tell me what I'm doing wrong or point me in the right direction?
Thanks
I finally figured out a solution. Hope it will help someone someday.
Complete solution except some easy-to-figure-out methods are posted in this gist. Bing-Mail Easy Post Api - Version 1.3
What i did was modified the DigestAuthFixer from https://stackoverflow.com/a/3117042/959245 to support any HTTP method.
Then used that to create the session, when we create the session using DigestAuthFixer it stores the Digest-Auth headers which i can reuse when uploading the files.
using (var client = new WebClient())
{
var uri = new Uri(_easypostHosts[2] + UploadUri.FormatWith(sessionId, HttpUtility.UrlEncode(fileName)));
// get the auth headers which are already stored when we create the session
var digestHeader = DigestAuthFixer.GetDigestHeader(uri.PathAndQuery, "PUT");
// add the auth header to our web client
client.Headers.Add("Authorization", digestHeader);
// trying to use the UploadFile() method doesn't work in this case. so we get the bytes and upload data directly
byte[] fileBytes = File.ReadAllBytes(filePath);
// as a PUT request
var result = client.UploadData(uri, "PUT", fileBytes);
// result is also a byte[].
content = result.Length.ToString();
}
I have three applications.
First: IIS
Second: Service (ASP.NET MVC)
Third: Client(Winform)
Files are store on IIS. Service public an api to download file as byte array base on URL. Client call api of Service and store file by extension.
After Client call Service, I check on Service, it return 15500 bytes. But I catch on Client, it is 13 bytes.
Below is the code on Service:
[HttpGet]
public byte[] DownloadData(string serverUrlAddress, string path)
{
if (string.IsNullOrWhiteSpace(serverUrlAddress) || string.IsNullOrWhiteSpace(path))
return null;
// Create a new WebClient instance
using (WebClient client = new WebClient())
{
// Concatenate the domain with the Web resource filename.
string url = string.Concat(serverUrlAddress, "/", path);
if (url.StartsWith("http://") == false)
url = "http://" + url;
byte[] data = client.DownloadData(url);
return data;
}
}
Below is the code on Client:
static void Main(string[] args)
{
byte[] data = GetData();
File.WriteAllBytes(#"E:\a.pdf", data);
}
public static byte[] GetData()
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:54220/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("API/File/DownloadData?serverUrlAddress=www.x.com&path=Data/Folder/file.pdf").Result;
if (response.IsSuccessStatusCode)
{
var yourcustomobjects = response.Content.ReadAsByteArrayAsync().Result;
return yourcustomobjects;
}
else
{
return null;
}
}
}
When returning CLR types Web API attempts to serialize the object based on either Xml or Json serializers. Neither of these are what you want. You want to return the raw stream of bytes. Try this.
[HttpGet]
public HttpResponseMessage DownloadData(string serverUrlAddress, string path)
{
if (string.IsNullOrWhiteSpace(serverUrlAddress) || string.IsNullOrWhiteSpace(path))
return null;
// Create a new WebClient instance
using (WebClient client = new WebClient())
{
// Concatenate the domain with the Web resource filename.
string url = string.Concat(serverUrlAddress, "/", path);
if (url.StartsWith("http://") == false)
url = "http://" + url;
byte[] data = client.DownloadData(url);
return new HttpResponseMessage() { Content = new StreamContent(data) };
}
}
By returning a HttpResponseMessage you have more control over exactly how Web API returns the response. By default StreamContent will set the Content-Type header to be application/octet-stream. You may want to change that to 'application/pdf' if you are always returning PDF files.