SignalR .Net client fails to connect (upd: how to set auth. cookie?) - asp.net-mvc-3

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"];

Related

Using Network Creds in dotnet core app on a mac using HttpClient

Writing a dotnet core app. I need to log in with network credentials as the service (which happens to be a TFS on-prem server) uses those to authenticate. From my (and another team members') windows machine, the following code works:
Console.WriteLine("Type in your DOMAIN password:");
var pass = GetPassword(); //command line secure string magic from SO
var networkCredential = new NetworkCredential("USERNAME", pass, "DOMAINNAME");
string tfsDefaultCollection = "https://TFSURL/DefaultCollection";
string testUrl = $"{tfsDefaultCollection}/_apis/tfvc/changesets/1234/changes?api-version=2.2";
var httpClientHandler = new HttpClientHandler
{
Credentials = networkCredential
};
var client = new HttpClient(httpClientHandler)
{
BaseAddress = new Uri(testUrl)
};
httpClientHandler.PreAuthenticate = true;
var test = client.GetAsync(testUrl).Result;
Console.WriteLine(test);
But it doesn't work from my mac. I get a 401 unauthorized. Both used the same, hardwired connection. AND this works on my mac:
curl --ntlm --user "DOMAINNAME\USERNAME" "https://TFSURL/DefaultCollection/_apis/tfvc/changesets/1234/changes?api-version=2.2"
So that rules out a connectivity question, I would think. Am I missing something I need to be doing on my mac? Can anybody point me to some documentation or way to troubleshoot what both of these requests are doing at the lowest level to see if there is a difference?
Well finally some google-foo got me there. There's a bug in dotnet core for linux/mac. This issue describes the fix:
https://github.com/dotnet/corefx/issues/25988#issuecomment-412534360
It has to do with the host machine you are connecting to uses both Kerberos and NTLM authentication methods.
Implemented below:
AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);
Console.WriteLine("Type in your DOMAIN password:");
var pass = GetPassword(); //command line secure string magic from SO
var networkCredential = new NetworkCredential("USERNAME", pass, "DOMAINNAME");
string tfsDefaultCollection = "https://TFSURL/DefaultCollection";
string testUrl = $"{tfsDefaultCollection}/_apis/tfvc/changesets/1234/changes?api-version=2.2";
var myCache = new CredentialCache
{
{
new Uri(testUrl), "NTLM",
networkCredential
}
};
var httpClientHandler = new HttpClientHandler
{
Credentials = myCache
};
var client = new HttpClient(httpClientHandler)
{
BaseAddress = new Uri(testUrl)
};
httpClientHandler.PreAuthenticate = true;
var test = client.GetAsync(testUrl).Result;
Console.WriteLine(test);
Thanks to #dmcgill50 for getting me on the right googling track.

WebRequest returns 404 when switching to SSL

Having built an app using PCL method in Xamarin and have had it working 100% using standard HTTP I now changed the remote test server to use SSL with self signed certs.
The app contacts a custom API for logging onto a server and querying for specific data.
I've changed the app to look at SSL now and initially got an error regarding Authentication not working or something but turned off SSL related errors for testing using:
ServicePointManager.ServerCertificateValidationCallback += (o, certificate, chain, errors) => true;
in my AppDelegate files FinishedLaunching method which got over that error.
I'm now getting a 404 / protocol error when trying to do my Login POST to the given URL.
I am using HttpWebRequest for my RESTful calls and this works fine if I change back to plain http.
Not sure why but some articles suggested using ModernHttpClient, which I did. I imported the component (also added the package using NuGet) to no avail.
Am I missing something else that I should be configuring in my code related to httpwebresponse when contacting the SSL server or is this component simply incapable of speaking to an SSL server?
My login function is as follows (Unrelated code removed/obfuscated):
public JsonUser postLogin(string csrfToken, string partnerId, string username, string password){
string userEndPoint = SingletonAppSettngs.Instance ().apiEndPoint;
userEndPoint = userEndPoint.Replace ("druid/", "");
var request = WebRequest.CreateHttp(string.Format(this.apiBaseUrl + userEndPoint + #"user/login.json"));
// Request header collection set up
request.ContentType = "application/json";
request.Headers.Add ("X-CSRF-Token", csrfToken);
// Add other configs
request.Method = "POST";
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
string json_body_content = "{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}";
streamWriter.Write(json_body_content);
streamWriter.Flush();
streamWriter.Close();
}
try{
HttpWebResponse httpResponse = (HttpWebResponse)request.GetResponse();
using (StreamReader reader = new StreamReader (httpResponse.GetResponseStream ())) {
var content = reader.ReadToEnd ();
content = content.Replace ("[],", "null,");
content = content.Replace ("[]", "null");
if (content == null) {
throw new Exception ("request_post_login - content is NULL");
} else {
JsonSerializerSettings jss = new JsonSerializerSettings();
jss.NullValueHandling = NullValueHandling.Ignore;
JsonUser deserializedUser = JsonConvert.DeserializeObject<JsonUser>(content, jss);
if(content.Contains ("Hire company admin user")){
deserializedUser.user.roles.__invalid_name__5 = "Hire company admin user";
deserializedUser.user.roles.__invalid_name__2 = "authenticated user";
}
return deserializedUser;
}
}
}catch(Exception httpEx){
Console.WriteLine ("httpEx Exception: " + httpEx.Message);
Console.WriteLine ("httpEx Inner Exception: " + httpEx.InnerException.Message);
JsonUser JsonUserError = new JsonUser ();
JsonUserError.ErrorMessage = "Error occured: " + httpEx.Message;
return JsonUserError;
}
}
When making a Web Request using ModernHttpClient, I generally follow the pattern below. Another great library created by Paul Betts is refit, and can be used to simplify rest calls.
using (var client = new HttpClient(new NativeMessageHandler(false, false)))
{
client.BaseAddress = new Uri(BaseUrl, UriKind.Absolute);
var result = await Refit.RestService.For<IRestApi>(client).GetData();
}
The second parameter for NativeMessageHandler should be set to true if using a customSSLVerification.
Here's a look at IRestApi
public interface IRestApi
{
[Get("/foo/bar")]
Task<Result> GetMovies();
}
Number of things I had to do to get this to work.
The Self Signed Cert had to allow TLS 1.2
As the API is Drupal based, HTTPS had to be enabled on the server and a module installed to manage the HTTP specific pages.

Windows.Web.Http.HttpClient + WEB API Windows Authentication

Im using Windows.Web.Http.HttpClient to connect to my WEB API. Application haven't prompted for userid and password, but recently i changed WEB API by moving AuthorizeAttribute filter from Action to Class level. Now my Windows store 8.1 application prompt for user id and password. Please let me know how to set HttpClient to not prompt the login and password. Can any1 suggest me do i need to add header to my httpcleint
using (Windows.Web.Http.HttpClient httpClient = new Windows.Web.Http.HttpClient())
{
// Add a user-agent header
var headers = httpClient.DefaultRequestHeaders;
// The safe way to check a header value from the user is the TryParseAdd method
// Since we know this header is okay, we use ParseAdd with will throw an exception
// with a bad value - http://msdn.microsoft.com/en-us/library/windows/apps/dn440594.aspx
headers.UserAgent.ParseAdd("ie");
headers.UserAgent.ParseAdd("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)");
using (var response = await httpClient.GetAsync(new Uri(url)))
I dont see a way to send Default credentials.
Disable UI dialogs using HttpBaseProtocolFilter.AllowUI. Try this:
Windows.Web.Http.Filters.HttpBaseProtocolFilter filter =
new Windows.Web.Http.Filters.HttpBaseProtocolFilter();
filter.AllowUI = false;
HttpClient client = new HttpClient(filter);
Uri uri = new Uri("http://localhost/?basic=1");
var response = await client.GetAsync(uri);
System.Diagnostics.Debug.WriteLine(response);
Do you need credentials? Use HttpBaseProtocolFilter.ServerCredential. Try this:
Uri uri = new Uri("http://localhost?ntlm=1");
Windows.Web.Http.Filters.HttpBaseProtocolFilter filter =
new Windows.Web.Http.Filters.HttpBaseProtocolFilter();
filter.AllowUI = false;
// Set credentials that will be sent to the server.
filter.ServerCredential =
new Windows.Security.Credentials.PasswordCredential(
uri.ToString(),
"userName",
"abracadabra");
HttpClient client = new HttpClient(filter);
var response = await client.GetAsync(uri);
System.Diagnostics.Debug.WriteLine(response);
Do you need default Windows credentials (domain credentials)? Simply add the Enterprise Authentication capability to your Package.appxmanifest.
I tried to apply your solution but it doesn't work as expected, or maybe I don't understand what I'm supposed to do.
I need to user the Windows credentials and I have enabled the Enterprise Authentification capability on my UWP app.
I use the code that you suggest:
var filter = new HttpBaseProtocolFilter();
filter.AllowUI = false;
var client = new HttpClient(filter);
HttpResponseMessage response = new HttpResponseMessage();
response = await client.PostAsync(concUri, null);
But the response returns me a 401.3 error...
If I add the login/password to the ServerCredential, this works well:
var filter = new HttpBaseProtocolFilter();
filter.AllowUI = false;
filter.ServerCredential = new Windows.Security.Credentials.PasswordCredential(WebServiceConstants.WebServiceUrl.ToString(), "login", "password");
var client = new HttpClient(filter);
HttpResponseMessage response = new HttpResponseMessage();
response = await client.GetAsync(concUri);
But I don't see what is the role of the Enterprise Authentication capability in this case, if I need to pass the login and the password...

Thinktecture IdentityModel AuthenticationConfiguration Mapping for Cookie - how?

I have a Web API based application currently set up using the amazing Thinktecture IdentityModel 4.5.
It is set up for claims-based authentication, accepting a Basic auth credential sent in on the Authorization header. The javascript client saves the returned session token and uses this for subsequent requests by including it in the Authorization header preceded by Session as the scheme.
The javascript client also saves the token to a cookie, for retrieval if the window is closed and reopened quickly, or when new windows are opened to prevent the user having to re-authenticate. The cookie is named sessionToken and it's value is the actual token.
It all works wonderfully well.
The problem is I have a link on the app page that links to a direct address (/api/controller/id/pdfdocument) and opens it in a new window (target: _blank). Therefore there is no way to include the Authorization header in this request. However, the cookie is transferred over correctly as the session is still active.
I have tried to add a mapping to the AuthenticationConfig.Mappings collection to add support for collecting the token from the cookie, however I just can't get the configuration right to get this working, and havn't been able to find any other resources online. I'm assuming there's something very simple that needs to get fixed.
My code:
private static AuthenticationConfiguration CreateAuthenticationConfiguration()
{
var sessionTokenConfiguration = new SessionTokenConfiguration();
sessionTokenConfiguration.EndpointAddress = "/Authenticate";
sessionTokenConfiguration.DefaultTokenLifetime = new TimeSpan(1, 0, 0);
var authenticationConfig = new AuthenticationConfiguration
{
ClaimsAuthenticationManager = _authenticationManager,
RequireSsl = false,
EnableSessionToken = true,
SessionToken = sessionTokenConfiguration,
SendWwwAuthenticateResponseHeaders = false
};
var securityTokenHandler = new Thinktecture.IdentityModel.Tokens.Http.BasicAuthenticationWithRoleSecurityTokenHandler(_userService.ValidateUser, _userService.GetRolesForUser);
securityTokenHandler.RetainPassword = false;
var realm = "localhost";
var authorizationMapping = new AuthenticationOptionMapping
{
Options = AuthenticationOptions.ForAuthorizationHeader(scheme: "Basic"),
TokenHandler = new System.IdentityModel.Tokens.SecurityTokenHandlerCollection { securityTokenHandler },
Scheme = AuthenticationScheme.SchemeAndRealm("Basic", realm)
};
authenticationConfig.AddMapping(authorizationMapping);
var cookieMapping = new AuthenticationOptionMapping
{
Options = AuthenticationOptions.ForCookie("sessionToken"),
TokenHandler = new System.IdentityModel.Tokens.SecurityTokenHandlerCollection { securityTokenHandler },
Scheme = AuthenticationScheme.SchemeOnly(scheme: "Session")
};
authenticationConfig.AddMapping(cookieMapping);
//authenticationConfig.AddBasicAuthentication(_userService.ValidateUser, _userService.GetRolesForUser);
return authenticationConfig;
}
This configuration is then applied like so:
HttpConfiguration config;
var authenticationConfig = CreateAuthenticationConfiguration();
config.MessageHandlers.Add(new AuthenticationHandler(authenticationConfig));
And this is what the cookie looks like in the request header:
Cookie: sessionToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEzNzM2NDA5NjgsImlzcyI6InNlc3Npb24gaXNzdWVyIiwiYXVkIjoiaHR0cDovL3Nlc3Npb24udHQvIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZSI6ImEiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL2F1dGhlbnRpY2F0aW9ubWV0aG9kIjoiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2F1dGhlbnRpY2F0aW9ubWV0aG9kL3Bhc3N3b3JkIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9hdXRoZW50aWNhdGlvbmluc3RhbnQiOiIyMDEzLTA3LTEyVDEzOjU2OjA4LjA5N1oiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmlzdHJhdG9yIiwiSWQiOiIyIn0.UlPeD9HzduQfwHE7NuXi9eMVo40hypi_LBK-f76VYFI; username=a
Any help most appreciated!
So after waiting a few minutes and receiving no replies and desperately needing this functionality I dived into the Thinktecture IdentityModel 4.5 source code to see what was going on and it seems this feature is not actually supported. Not only is it not supported but from the looks of it cookie mapping is not actually implemented.
I forked the repository and made a few small changes to allow for this feature:
https://github.com/ibraheemhlaiyil/Thinktecture.IdentityModel.45
and sent Dominick Baier of Thinktecture this in a pull request:
https://github.com/thinktecture/Thinktecture.IdentityModel.45/pull/95
Cookie usage has it's disadvantages, and it seems Thinktecture are trying to stay away from them as far as possible, however I could not come up with a different solution to my problem - a javascript client web applications that needs to open a new window/tab and maintain the authenticated session in the new window/tab.
If you want to use this feature, you simply set the new CookieName property on the SessionTokenConfiguration object. IdentityModel uses the HeaderName property to determine which header to look up for authentication data. In the same way, if the CookieName property is set this determines which cookie name is looked up for authentication data if no authentication data was found on the header.
In the example below, authentication data is looked for on the cookie named sessionToken if no authentication data is found on the Authorization header.
private static AuthenticationConfiguration CreateAuthenticationConfiguration()
{
var authenticationConfig = new AuthenticationConfiguration
{
ClaimsAuthenticationManager = _authenticationManager,
RequireSsl = false,
SendWwwAuthenticateResponseHeaders = false,
EnableSessionToken = true,
SessionToken = new SessionTokenConfiguration
{
EndpointAddress = "/Authenticate",
DefaultTokenLifetime = new TimeSpan(1, 0, 0),
HeaderName = "Authorization",
CookieName = "sessionToken",
SigningKey = CryptoRandom.CreateRandomKey(32)
}
};
authenticationConfig.AddBasicAuthentication(_userService.ValidateUser, _userService.GetRolesForUser);
return authenticationConfig;
}
As before, this configuration is applied like so during your application start up:
HttpConfiguration config;
var authenticationConfig = CreateAuthenticationConfiguration();
config.MessageHandlers.Add(new AuthenticationHandler(authenticationConfig));
The cookie authentication data has the exact same form as the data sent in the Authorization header, so if sent, the cookie should look like:
Cookie: sessionToken=Session eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjEzNzM2NDA5NjgsImlzcyI6InNlc3Npb24gaXNzdWVyIiwiYXVkIjoiaHR0cDovL3Nlc3Npb24udHQvIiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZSI6ImEiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL2F1dGhlbnRpY2F0aW9ubWV0aG9kIjoiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2F1dGhlbnRpY2F0aW9ubWV0aG9kL3Bhc3N3b3JkIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9hdXRoZW50aWNhdGlvbmluc3RhbnQiOiIyMDEzLTA3LTEyVDEzOjU2OjA4LjA5N1oiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmlzdHJhdG9yIiwiSWQiOiIyIn0.UlPeD9HzduQfwHE7NuXi9eMVo40hypi_LBK-f76VYFI
Hope someone finds this of some use!

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