AndroidClientHandler and built-in Basic authentication - xamarin

I'm trying to make the built-in Basic Authentication work with the Xamarin AndroidClientHandler, but with no success. The code looks like this:
https://github.com/tieto-sternell/DemoAndroidBasicAuth/blob/c6bb4d547f2456e66b25daca4951957250278ac3/DemoAndroidBasicAuth/DemoAndroidBasicAuth.Droid/TestStuff.cs#L39-L43
ICredentials credentials = new NetworkCredential(dummyUsername, dummyPassword);
var handler = new AndroidClientHandler();
handler.Credentials = credentials;
and:
https://github.com/tieto-sternell/DemoAndroidBasicAuth/blob/c6bb4d547f2456e66b25daca4951957250278ac3/DemoAndroidBasicAuth/DemoAndroidBasicAuth.Droid/TestStuff.cs#L19-L21
var client = new HttpClient(handler);
var badResponse = await client.GetAsync(basicUri) as AndroidHttpResponseMessage;
As you can see, it is pretty straight-forward and the code looks very similar to its working .Net equivalent. The response is 401, though, so I am doing something wrong.
Edit: As #jgoldberger points out below, it is possible to create the headers manually. This can be done either by adding a header to the message (github.com/tieto-sternell/DemoAndroidBasicAuth/blob/e7118f88a5b45f91207e90e9ea64c554d0ea9cd6/DemoAndroidBasicAuth/DemoAndroidBasicAuth.Droid/TestStuff.cs#L43-L48)
byte[] byteToken = System.Text.Encoding.UTF8.GetBytes(dummyUsername + ":" + dummyPassword);
var tokenValue = Convert.ToBase64String(byteToken);
var token = "Basic " + tokenValue;
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, new Uri(basicUri));
requestMessage.Headers.Add("Authorization", token.ToString());
Or by adding a header to the HttpClient (github.com/tieto-sternell/DemoAndroidBasicAuth/blob/e7118f88a5b45f91207e90e9ea64c554d0ea9cd6/DemoAndroidBasicAuth/DemoAndroidBasicAuth.Droid/TestStuff.cs#L33-L37)
byte[] byteToken = System.Text.Encoding.UTF8.GetBytes(dummyUsername + ":" + dummyPassword);
var tokenValue = Convert.ToBase64String(byteToken);
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", tokenValue);
I don't think that this is how it is supposed to work, though. I mean, when is this (github.com/xamarin/xamarin-android/blob/0c3597869bc4493895e755bda8a26f778e4fe9e0/src/Mono.Android/Xamarin.Android.Net/AuthModuleBasic.cs#L50-L52)
response += cred.UserName + ":" + cred.Password;
return new Authorization ($"{AuthenticationType} {Convert.ToBase64String (Encoding.ASCII.GetBytes (response))}");
code supposed to run in that case?
It looks to me as if the Authentication module isn't used.
The background story here is that I need to know how to make Basic work as part of a larger problem which involves Digest authentication and the custom "AuthenticationScheme.Unsupported", but we'll get there later when we've got the simpler basic auth scheme up and running.
Best regards,
Christian

Related

client.DeleteAsync - include object in body

I have an ASP.NET MVC 5 website - in C# client code I am using HttpClient.PutAsJsonAsync(path, myObject) fine to call a Json API (the API is also mine created in Web API).
client.BaseAddress = new Uri("http://mydomain");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.PutAsJsonAsync("api/something", myObj);
I would like to do the same with a Delete verb. However client.DeleteAsync does not allow an object to be passed in the body. (I would like to record the reason for deletion alongside the Id of the item to delete in the URI).
Is there a way to do this?
You'll have to give up a little in terms of convenience since the higher-level DeleteAsync doesn't support a body, but it's still pretty straightforward to do it the "long way":
var request = new HttpRequestMessage {
Method = HttpMethod.Delete,
RequestUri = new Uri("http://mydomain/api/something"),
Content = new StringContent(JsonConvert.SerializeObject(myObj), Encoding.UTF8, "application/json")
};
var response = await client.SendAsync(request);

Very simple web API app - 2 methods - hard code basic authentication

I have a very simple Web API app. Just 2 methods that put (edit or insert data) - I'd like to just hard code basic authentication into the 2 methods without hugely complicating it.
What is the minimum code I need in order to require authentication the two methods - and then how do I use this code (the APIs are called from some C# code in an MVC app).
All of the examples I find online are hugely complicated - with filters or overly elaborate authentication.
My web API methods look something like this:
public IHttpActionResult Put([FromBody]ImportObject impobj)
{
// Import data here
}
And I call it like this:
public async static Task<string> Caller(ImportObject impobj)
{
string responseString = "NA";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Post new object, get response
var response = await client.PutAsJsonAsync("myservice/api/meth", impobj);
// Get response content string
var resContent = response.Content.ReadAsStringAsync();
// Build user response string (just for testing)
responseString =
"<br />result: " + resContent.Result.ToString() +
"<br />status: " + resContent.Status.ToString() +
"<br />StatusCode: " + response.StatusCode + "<br />";
}
// Output response string
return responseString;
}
I just want to add basic auth to both ends - as it's so simple happy to just hard code it into both methods (this will also let me see easily how it works too).
thx.

Get "API key is missing" error when querying account details to Mailchimp API 3.0 using RestSharp

When using RestSharp to query account details in your MailChimp account I get a "401: unauthorized" with "API key is missing", even though it clearly isn't!
We're using the same method to create our RestClient with several different methods, and in all requests it is working flawlessly. However, when we're trying to request the account details, meaning the RestRequest URI is empty, we get this weird error and message.
Examples:
private static RestClient CreateApi3Client(string apikey)
{
var client = new RestClient("https://us2.api.mailchimp.com/3.0");
client.Authenticator = new HttpBasicAuthenticator(null, apiKey);
return client;
}
public void TestCases() {
var client = CreateApi3Client(_account.MailChimpApiKey);
var req1 = new RestRequest($"lists/{_account.MailChimpList}/webhooks", Method.GET);
var res1 = client.Execute(req1); // works perfectly
var req2 = new RestRequest($"automations/{account.MailChimpTriggerEmail}/emails", Method.GET);
var res2 = client.Execute(req2); // no problem
var req3 = new RestRequest(Method.GET);
var res3 = client.Execute(req3); // will give 401, api key missing
var req4 = new RestRequest(string.Empty, Method.GET);
var res4 = client.Execute(req4); // same here, 401
}
When trying the api call in Postman all is well. https://us2.api.mailchimp.com/3.0, GET with basic auth gives me all the account information and when debugging in c# all looks identical.
I'm trying to decide whether to point blame to a bug in either RestSharp or MailChimp API. Has anyone had a similar problem?
After several hours we finally found what was causing this..
When RestSharp is making the request to https://us2.api.mailchimp.com/3.0/ it's opting to omit the trailing '/'
(even if you specifically add this in the RestRequest, like: new RestRequest("/", Method.GET))
so the request was made to https://us2.api.mailchimp.com/3.0
This caused a serverside redirect to 'https://us2.api.mailchimp.com/3.0/' (with the trailing '/') and for some reason this redirect scrubbed away the authentication header.
So we tried making a
new RestRequest("/", Method.GET)
with some parameters (req.AddParameter("fields", "email")) to make it not scrub the trailing '/', but this to was failing.
The only way we were able to "fool" RestSharp was to write it a bit less sexy like:
new RestRequest("/?fields=email", Method.GET)

SignalR .Net client fails to connect (upd: how to set auth. cookie?)

This thing is dragging me nuts.
I have a .net 4.0 console app and I have an MVC web app.
javascript clients can connect and talk to the server - no problems here...
but my .net client throws System.AggregateException with InnerException = "Unexpected character encountered while parsing value: <. Path...
so I created an empty MVC3 app, added SignalR libraries, and .net client surprisingly connects to that. But for some reason it doesn't to the other one. I've checked everything, both MVC3 apps, both use the same SignalR libs, the same NewtonsoftJson... I thought it must be something with the routing, I guess no - js client works.
var connection = new HubConnection("http://localhost:58746");
var hubProxy = connection.CreateProxy("myProxy");
connection.Start().Wait() // it fails here on Wait
What could it be?
UPD: I have figured... it's because FormsAuthentication on the server. Now is there any way to feed .ASPXAUTH cookie to SignalR so it can connect to the server?
The solution by Agzam was really helpful, but if anyone else uses the posted code it is critical that you close the HttpWebResponse before exiting GetAuthCookie. If you don't you will find that whenever you use SignalR to invoke a method on the server, the request (under most circumstances) will queue indefinitely on the client and will neither succeed nor fail.
Note. The original code worked in the test environment when everything was on my PC, but failed consistently when the website was hosted on a remote server.
here is the modified code I ended up using
private Cookie GetAuthCookie(string user, string pass)
{
var http = WebRequest.Create(_baseUrl+"Users/Login") as HttpWebRequest;
http.AllowAutoRedirect = false;
http.Method = "POST";
http.ContentType = "application/x-www-form-urlencoded";
http.CookieContainer = new CookieContainer();
var postData = "UserName=" + user + "&Password=" + pass + "&RememberMe=true&RememberMe=false&ReturnUrl=www.google.com";
byte[] dataBytes = System.Text.Encoding.UTF8.GetBytes(postData);
http.ContentLength = dataBytes.Length;
using (var postStream = http.GetRequestStream())
{
postStream.Write(dataBytes, 0, dataBytes.Length);
}
var httpResponse = http.GetResponse() as HttpWebResponse;
var cookie = httpResponse.Cookies[FormsAuthentication.FormsCookieName];
httpResponse.Close();
return cookie;
}
its a very minor change , but it will save you a lot of debugging time.
Ok... stupid me... SignalR failed to connect because it cannot breach server's Forms authentication. So what needed to be done is to get the auth cookie and stick it to the HubConnection.CookieContainer...
so I wrote this method method to login with a username and get the cookie:
private Cookie GetAuthCookie(string user, string pass)
{
var http = WebRequest.Create(_baseUrl+"Users/Login") as HttpWebRequest;
http.AllowAutoRedirect = false;
http.Method = "POST";
http.ContentType = "application/x-www-form-urlencoded";
http.CookieContainer = new CookieContainer();
var postData = "UserName=" + user + "&Password=" + pass + "&RememberMe=true&RememberMe=false&ReturnUrl=www.google.com";
byte[] dataBytes = System.Text.Encoding.UTF8.GetBytes(postData);
http.ContentLength = dataBytes.Length;
using (var postStream = http.GetRequestStream())
{
postStream.Write(dataBytes, 0, dataBytes.Length);
}
var httpResponse = http.GetResponse() as HttpWebResponse;
var cookie = httpResponse.Cookies[FormsAuthentication.FormsCookieName];
httpResponse.Close();
return cookie;
}
And used it like this:
var connection = new HubConnection(_baseUrl)
{
CookieContainer = new CookieContainer()
};
connection.CookieContainer.Add(GetAuthCookie(_user, _pass));
Works perfectly!
Just use this for reading cookies:
var cookie = response.Cookies[".AspNet.ApplicationCookie"];

WebClient NotFound error but working with HttpWebRequest/Response

In my WinPhone app I'm accessing a REST service.
At the beginnings I was using this code:
WebClient wc = new WebClient();
wc.Credentials = credentials;
wc.Headers["App-Key"] = appKey;
wc.DownloadStringCompleted +=
(o, args) => MessageBox.Show(args.Error == null ? "OK" : "Error");
wc.DownloadStringAsync(uri);
but it suddenly stopped working returning me a "The remote server returned an error: NotFound" error. After a google session and some clicks in the control panel, I didn't get it to work.
I decided to try this other way:
HttpWebRequest request = HttpWebRequest.CreateHttp(uri);
request.Credentials = credentials;
request.Headers["App-Key"] = appKey;
request.BeginGetResponse(asResult =>
{
var response = request.EndGetResponse(asResult) as HttpWebResponse;
StreamReader reader = new StreamReader(response.GetResponseStream());
string responseString = reader.ReadToEnd();
Dispatcher.BeginInvoke(
() => MessageBox.Show(response.StatusCode.ToString()));
}, null);
and it works.
I also tried to run the first snipped pointing the URI to google's home page and it works (I had to remove the credentials, of course).
Can anyone explain what's going on?
UPDATE
I managed to get it working by replacing the
wc.Credentials = new NetworkCredentials(username, password);
with
wc.Headers["Authorization"] = "Basic someBase64encodedString";
but i still wonder what happened and which are the differences between the first and the second line.
PS: the test URI is: https://api.pingdom.com/api/2.0/checks but you will need an app-key from them.
When using the Credentials property, the HttpWebRequest implementation will wait the challenge response from server before to send the 'Authorization' header value.
But this can be an issue in some cases, so you have to force Basic authentication by providing directly the Authorization header.
Example when using a REST Client library like Spring.Rest :
RestTemplate template = new RestTemplate("http://example.com");
template.RequestInterceptors.Add(new BasicSigningRequestInterceptor("login", "password"));
string result = template.GetForObject<string>(uri);

Resources