I am trying to use an Api to get the value of some metrics with a c# application. My code looks like this:
var httpWebRequest = (HttpWebRequest)WebRequest.Create("http://myServer/api/measures/component?componentKey=myProject&metricKeys=bugs,new_bugs");
httpWebRequest.Headers.Add("Authorization", token);
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "GET";
try
{
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
string result;
using (var streamreader = new StreamReader(httpResponse.GetResponseStream()))
{
result = streamreader.ReadToEnd();
}
var joResponse = JObject.Parse(result);
var measures = (JArray)joResponse["component"]["measures"];
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
When i write the same API into the address box of my browser, i get the result i looked for. And the token for the header is generated by my server. But for some reason I catch an error 401 when trying to get the response for this httpwebrequest in the code. What am I doing wrong?
Alternatively, instead of using a token, I tried using credentials:
httpWebRequest.Credentials = new NetworkCredential(name, password);
The result was the same.
EDIT:
I tried to use the header
httpWebRequest.Headers.Add("Authorization", "Bearer " + token);
instead. This also didn't work out.
I had this problem last week and couldn't believe what I've experienced.
In the "Authorization" header you have to encode the token with an empty password like this:
"Authorization", $"Basic {Convert.ToBase64String(Encoding.ASCII.GetBytes(apiKey + ":"))}"
The colon after "apiKey" is very important.
Related
I am developing a C# application which needs to use the onelogin API to retrieve a session token. I am able to authenticate and and create a token with the following code:
WebRequest Authrequest = WebRequest.Create("https://api.us.onelogin.com/auth/oauth2/token");
Authrequest.Method = "POST";
Authrequest.ContentType = "application/json";
Authrequest.Headers.Add("cache-control", "no-cache");
Authrequest.Headers.Add("Authorization: client_id:XXXXXXX7bbf2c50200d8175206f664dc28ffd3ec66eef0bfedb68c3366420dc, client_secret:XXXXXXXXXX6ba2802187feb23f6450c6812b8e6639361d24aa83f12010f ");
using (var streamWriter = new StreamWriter(Authrequest.GetRequestStream()))
{
string Authjson = new JavaScriptSerializer().Serialize(new
{
grant_type = "client_credentials"
});
streamWriter.Write(Authjson);
}
WebResponse AuthReponse;
AuthReponse = Authrequest.GetResponse();
Stream receiveStream = AuthReponse.GetResponseStream ();
// Pipes the stream to a higher level stream reader with the required encoding format.
StreamReader readStream = new StreamReader (receiveStream);
JObject incdata = JObject.Parse(readStream.ReadToEnd());
string sToken = incdata["data"][0]["access_token"].Value<string>();
AuthReponse.Close();
However, when running the Create Session Login Token with the following code, it only returns a 400 error, and the message has no detail. Just Bad Request:
//Get the session token for the specified user, using the token recieved from previous web request
WebRequest request = WebRequest.Create("https://api.us.onelogin.com/api/1/login/auth");
request.Method = "POST";
request.ContentType = "application/json";
request.Headers.Add("authorization", "bearer:" + sToken);
using (var streamWriter2 = new StreamWriter(request.GetRequestStream()))
{
string json = JsonConvert.SerializeObject(new
{
username_or_email = sUsername,
password = sPassword,
subdomain = "comp-alt-dev"
});
streamWriter2.Write(json);
}
WebResponse response;
response = request.GetResponse();
string streamText = "";
var responseStream = response.GetResponseStream();
using (responseStream)
{
var streamReader = new StreamReader(responseStream);
using (streamReader)
{
streamText = streamReader.ReadToEnd();
streamReader.Close();
//
}
responseStream.Close();
}
Any ideas?
-Thank you
Also for anyone who may be getting this error. in C# the email is case sensitive. I tried User.email.com. In onelogin it was saved as user#email.com. changing the c# to lower case fixed it.
Can you let us know what payload you're sending across the wire to the .../1/login/auth endpoint as well as the response (either as others have suggested as packet snoop, or just as a debug output from the code)
400 means either bad json or the endpoint requires MFA, so this will narrow it down.
~thanks!
Just joining the troubleshooting effort =) -- I can replicate a 400 Bad Request status code with a "bad request" message when the request body contains a username_or_email and/or subdomain value that does not exist, or if the request body is empty.
Can you post what goes over the wire to the OneLogin endpoint...
OK Thanks. So it appears your subdomain does not exist. If you give me an email in the account I can find the correct subdomain value for you.
I'm developing a small app that pulls issues from JIRA from a given list of issue ids, however some of them don't exist and the API call returns a 400 Bad Request message.
This is the relevant block of code I have so far (C#):
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(thejiraurl);
request.Method = "GET";
request.ContentType = "Content-Type: application/json";
request.Accept = "application/json";
string mergedCredentials = "thecredentials :)";
byte[] byteCredentials = UTF8Encoding.UTF8.GetBytes(mergedCredentials);
string encodedCredentials = Convert.ToBase64String(byteCredentials);
request.Headers.Add("Authorization", "Basic " + encodedCredentials);
try
{
WebResponse response = request.GetResponse(); //this line breaks because some records don't exist anymore, that's ok, but at least I need to know which ones! those are found in the response that I can't see here but I can with Fiddler! #.#
Stream data = response.GetResponseStream();
}
catch(WebException ex)
{
Console.WriteLine(ex.Message);
if(ex.Status == WebExceptionStatus.ProtocolError)
{
Console.WriteLine("Status Code : {0}", ((HttpWebResponse)ex.Response).StatusCode); //This is 'BadRequest'
Console.WriteLine("Status Description : {0}", ((HttpWebResponse)ex.Response).StatusDescription); //This is 'Bad Request'
using (Stream responseStream = ex.Response.GetResponseStream())
using (StreamReader responseReader = new StreamReader(responseStream))
{
string responseMessage = responseReader.ReadToEnd(); //This is a long message but do not has the errormessages seen with Fiddler (and even in a internet browser)
Console.WriteLine(responseMessage);
}
}
}
However when I use Fiddler I can see the actual errormessages returned, that looks like:
{"errorMessages":["A value with ID '1' does not exist for the field 'key'."],"errors":{}}
How can I get this messages using C# or vb?
This is very similar to question, but that one is with Java: https://answers.atlassian.com/questions/147793/retrieving-errormessages-when-a-call-to-the-rest-api-fails-using-httpurlconnection
I appreciate any help.
I am using Stormpath for my authentication service. I call the RestAPI of Stormpath by using HttpWebRequest.
I am also using HttpWebRequest to call the RestAPI but it does not work.
private void BtnGetResetApiClick(object sender, EventArgs e)
{
var username = "aaaa";
var password = "bbbb";
ServicePointManager.ServerCertificateValidationCallback = Callback;
var request = WebRequest.Create("https://api.stormpath.com/v1/tenants/current") as HttpWebRequest;
request.UserAgent = ".NET SDK";
request.Method = "GET";
request.Accept = "*/*";
var data = string.Format("{0}:{1}", username, HttpUtility.HtmlEncode(password));
var token = Convert.ToBase64String(Encoding.UTF8.GetBytes(data));
string authHeader = string.Format("Basic {0}", token);
request.Headers.Add("Authorization", authHeader);
request.ServerCertificateValidationCallback = Callback;
using (var response = request.GetResponse())
{
var stream = response.GetResponseStream();
if (stream != null)
{
var streamReader = new StreamReader(stream);
var str = streamReader.ReadToEnd();
streamReader.Close();
stream.Close();
}
}
}
private bool Callback(object obj, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
return true;
}
When calling:
var response = request.GetResponse()
I got an exception:
An unhandled exception of type 'System.Net.WebException' occurred in System.dll The remote server returned an error: (401) Unauthorized.
Can you help me to see if my code has something wrong?
Update - use the SDK, it's much easier!
If you're calling the Stormpath API from C# frequently, don't bother with writing requests by hand. Use the Stormpath .NET SDK instead. I'm the author. :)
Install it using install-package Stormpath.SDK from the Package Manager Console. Then, create an IClient object:
// In a production environment, you'll want to load these from
// environment variables or a secure file, instead of hardcoding!
var apiKey = ClientApiKeys.Builder()
.SetId("Your_Stormpath_API_key_ID")
.SetSecret("Your_Stormpath_API_key_secret")
.Build();
var client = Clients.Builder()
.SetApiKey(apiKey)
.Build();
Getting the tenant info is now just a simple call:
var tenant = await client.GetCurrentTenantAsync();
Console.WriteLine($"Current tenant is: {tenant.Name}");
If you really want to make raw requests, you can still do that! I'll explain below.
Constructing the Authorization header
A 401 Unauthorized response means that the API was not able to find a valid Authorization header in your request. To authenticate correctly, you need two things:
An authorization payload in the format apiKeyID:apiKeySecret
An Authorization header with value: Basic base64(payload)
Here's how to construct the complete header:
// API_KEY_ID and API_KEY_SECRET populated elsewhere
var authPayload = string.Format("{0}:{1}", API_KEY_ID, API_KEY_SECRET);
var authPayloadEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(authPayload));
request.Headers.Add(HttpRequestHeader.Authorization, "Basic " + authPayloadEncoded);
You don't need the ServerCertificateValidationCallback = Callback stuff. With the above header, the request will be seen by the API as a valid request (assuming your API Key ID and Secret are correct, of course).
Redirection handling
One thing to watch out for (this tripped me up at first!) is that WebRequest will follow HTTP 302 redirects automatically, but will not apply the existing headers to the new request.
The solution is to disable redirect following:
request.AllowAutoRedirect = false;
This means you'll have to handle 302 responses yourself, but it's the only way to correctly apply the Authorization header to each request.
Working example
I created a simple working example in this gist. Since I'll be creating requests multiple times, I wrote a helper function:
private static HttpWebRequest BuildRequest(string method, string uri)
{
var request = WebRequest.Create(uri) as HttpWebRequest;
request.UserAgent = "dotnet/csharp web-request";
request.Method = method;
request.ContentType = "application/json";
// Important, otherwise the WebRequest will try to auto-follow
// 302 redirects without applying the authorization header to the
// subsequent requests.
request.AllowAutoRedirect = false;
// Construct HTTP Basic authorization header
var authPayload = string.Format("{0}:{1}", API_KEY_ID, API_KEY_SECRET);
var authPayloadEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(authPayload));
request.Headers.Add(HttpRequestHeader.Authorization, "Basic " + authPayloadEncoded);
return request;
}
And a simple console app to demonstrate getting the current tenant URL and name:
// Get these from the Stormpath admin console
private static string API_KEY_ID = "Your_Stormpath_API_key_ID";
private static string API_KEY_SECRET = "Your_Stormpath_API_key_secret";
static void Main(string[] args)
{
// First, we need to get the current tenant's actual URL
string tenantUrl = null;
var getCurrentTenantRequest = BuildRequest("GET", "https://api.stormpath.com/v1/tenants/current");
try
{
using (var response = getCurrentTenantRequest.GetResponse())
{
tenantUrl = response.Headers["Location"];
}
}
catch (WebException wex)
{
Console.WriteLine("Request failed. {0}", wex.Message);
throw;
}
// Now that we have the real tenant URL, get the tenant info
string tenantData = null;
var getTenantInfoRequest = BuildRequest("GET", tenantUrl);
try
{
using (var response = getTenantInfoRequest.GetResponse())
using (var responseStream = response.GetResponseStream())
using (var reader = new StreamReader(responseStream))
{
tenantData = reader.ReadToEnd();
}
}
catch (WebException wex)
{
Console.WriteLine("Request failed. {0}", wex.Message);
throw;
}
// Use JSON.NET to parse the data and get the tenant name
var parsedData = JsonConvert.DeserializeObject<Dictionary<string, object>>(tenantData);
Console.WriteLine("Current tenant is: {0}", parsedData["name"]);
// Wait for user input
Console.ReadKey(false);
}
The code is pretty verbose because we're making raw requests to the API. Again, if you're making requests frequently, use the SDK instead!
I am trying to retrieve an order from the Eventbrite API. I have a valid OAuth token and order number. I have verified this by using postman which successfully returns the correct JSON.
However when I make the call using the following c# code, I get a 401 Unauthorised:
var client = new HttpClient();
var req = new HttpRequestMessage(HttpMethod.Get, "https://www.eventbriteapi.com/v3/orders/{orderNo}");
req.Headers.Add("Authorization", "Bearer {authToken}");
var response = await client.SendAsync(req);
I've tried replacing the header with:
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("{authToken}");
I have also tried:
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://www.eventbriteapi.com/v3/orders/{orderNo}");
request.Headers.Add("Authorization", "Bearer {authToken}");
request.Accept = "application/json";
using(WebResponse response = request.GetResponse())
{
using(Stream dataStream = response.GetResponseStream())
{
using(StreamReader reader = new StreamReader(dataStream))
{
string responseFromServer = reader.ReadToEnd();
}
}
}
All of these get a 401 response.
I know the authtoken and the eventid are correct, so there must be something wrong with my code.
Am I doing something wrong with the authroisation token?
Have you tried ?token={authToken} option on the EventBrite API?
This would at least confirm if it's a problem with the way the header is being sent across.
http://developer.eventbrite.com/docs/auth/
You omitted the trailing '/' in the URL, which caused a subsequent redirect from "eventbriteapi.com/v3/orders/{orderNo}" to "eventbriteapi.com/v3/orders/{orderNo}/". The authorization header was dropped in the redirect.
I am not getting the results that documentation says. I login the Buddy; created application; copy this URL and assign to url string; when I execute the program I am not getting results that are expected (status + Accesstoken) as documentation says. Can anyone please tell me if I am missing something as newbie to http calls. Its running on http requester but not on Poster firefox add-on!
Documentation
http://dev.buddyplatform.com/Home/Docs/Getting%20Started%20-%20REST/HTTP?
Code
string parameters = "{appid:'xxxxxx', appkey: 'xxxxxxx', platform: 'REST Client'}";
private async void SimpleRequest()
{
HttpWebRequest request = null;
HttpWebResponse response = null;
try
{
request = (HttpWebRequest)WebRequest.Create(url);
request.Accept = "application/json";
request.ContentType = "application/json";
request.Method = "POST";
StreamWriter sw = new StreamWriter(await request.GetRequestStreamAsync());
sw.WriteLine(parameters);
sw.Close();
response = (HttpWebResponse) await request.GetResponseAsync();
}
catch (Exception)
{ }
}
Using the HTTP requester add-on on Firefox, I successfully retrieved an access token so their API work.
In C# they provide a line of code to submit your appid and appkey, that might be the problem :
Buddy.Init("yourAppId", "yourAppKey");
My guess is you have to use their .NET SDK!
You can certainly use the REST API from raw REST the way you're doing, though the .NET SDK will handle some of the more complex details of changing service root. I ran your code using my own Buddy credentials and I was able to get JSON containing an Access Token back. You may need to read the response stream back as JSON to retrieve the access token. I used the following code to dump the JSON to the console:
request = (HttpWebRequest)WebRequest.Create(url);
request.Accept = "application/json";
request.ContentType = "application/json";
request.Method = "POST";
StreamWriter sw = new StreamWriter(await request.GetRequestStreamAsync());
sw.WriteLine(parameters);
sw.Close();
response = (HttpWebResponse)await request.GetResponseAsync();
Console.WriteLine(await new StreamReader(response.GetResponseStream()).ReadToEndAsync());
Using Newtonsoft.Json I can parse out my accessToken like this:
Uri url = new Uri("https://api.buddyplatform.com/devices");
request = (HttpWebRequest)WebRequest.Create(url);
request.Accept = "application/json";
request.ContentType = "application/json";
request.Method = "POST";
StreamWriter sw = new StreamWriter(await request.GetRequestStreamAsync());
sw.WriteLine(parameters);
sw.Close();
response = (HttpWebResponse)await request.GetResponseAsync();
var parsed = JsonConvert.DeserializeObject<IDictionary<string,object>>( (await new StreamReader(response.GetResponseStream()).ReadToEndAsync()));
var accessToken = (parsed["result"] as JObject).GetValue("accessToken").ToString();
Console.WriteLine(accessToken);
The 3.0 SDK does all of this for you while exposing the rest of the service through a thin REST wrapper, the migration guide for the 3.0 SDK should help with this.