I have a local website (not mine) that requires authentication before doing some queries. The authentication header looks like this:
Host: 192.168.7.9
Connection: keep-alive
Content-Length: 185
Origin: http://192.168.7.9
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/27.0.1453.3 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: */*
DNT: 1
Referer: http://192.168.7.9/signin
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Cookie: _FProEnterprise_session=BAh7CzoVbmVlZF93ZWxjb21lX21zZ1Q6D3Nlc3Npb25faWQiJTUxNjI5OGRiMDNmNjU4ZDg4ODE3NmFiZjhmMDU3YTI2OglzaXRlSSIKc2l0ZTAGOgZFRjoObGFuZ19wYXRoSSIHZW4GOwhUOg5vbmVfY2xpY2tGOgx1c2VyX2lkaRE%3D--0c6634a714baa7f0e4795aee89b31f9b7ec0565e
And the request body looks like this:
username=myusername&password=mypassword
I'm not super great with how authentication works. So first, is this forms authentication? I'm guessing it is, since I have to enter my username and password on the site then submit to get in.
Second, why is there a Cookie already there? Is it from a previous session perhaps, and I can ignore it?
My goal is to reproduce this in C#, so that I can authenticate, get the cookie and then post data and retrieve results from this site. At least thats what I think I need to do. Links and code would be super helpful. If it's helpful I need to make this request from my web.api app controller.
You use asp.net membership provider and do the authentication like Membership.ValidateUser() and that will authenticate the formsauthentication also. Check if it is authenticated if (Context.User.Identity.IsAuthenticated) - FormsAuthentication.SignOut();
You need sql server or some kind of authentication mechanism first to save the username and password.
This seems to be an AJAX request (X-Requested-With: XMLHttpRequest). Therefore the user has to be on the web page first, which is when the session started. That is when the user gets the session cookie, which is sent every time to keep track of the session. This session is also kept on the server, where login information is stored - whether or not you're logged in, and who you are.
The contents seem to be a simple HTTP form, but since it came from an XMLHttpRequest it could just as well be created using Javascript. This is at least the standard way to send POST data through HTTP.
That is using plain HTTP authentication and the cookies are from an old session.
http://en.wikipedia.org/wiki/Basic_access_authentication
This link solved it for me:
HERE
My final code (in my web.api controller looked like this):
public static string JsonWithAuth( string url, string data )
{
var bytes = Encoding.Default.GetBytes( data );
using ( var client = new WebClientEx() )
{
var values = new NameValueCollection
{
{ "username", "myUsername" },
{ "password", "myPassword" },
};
// Authenticate
client.UploadValues( "http://192.168.7.9/main/signin", values );
// Post data
var response = client.UploadData( url, "POST", bytes );
return Encoding.Default.GetString( response );
}
}
And this was the class that made it work (from the linked answer):
/// <summary>
/// A custom WebClient featuring a cookie container
/// </summary>
public class WebClientEx : WebClient
{
public CookieContainer CookieContainer { get; private set; }
public WebClientEx()
{
CookieContainer = new CookieContainer();
}
protected override WebRequest GetWebRequest( Uri address )
{
var request = base.GetWebRequest( address );
if ( request is HttpWebRequest )
{
( request as HttpWebRequest ).CookieContainer = CookieContainer;
}
return request;
}
}
So my final call was like this:
string sampleInfo = JsonWithAuth(
"http://192.168.7.9/samples/sample_locations_list",
"sort=position&dir=ASC&box_id=");
Hope that helps someone else!
Related
I want to use this unmaintained project in order to authorize the services of my web api because it is mandatory for us to use Keycloak.
It has been quite easy to follow this tutorial and making it work in my web api but I cannot found any example of using tokens instead of interactive login.
The web api will receive with the http request an authorization header of our already logged client and the target is to securize the services and authorize them based on some roles.
I've perform some changes in Startup.cs - Configuration method:
ConfigureAuth(app);
app.UseKeycloakAuthentication(new KeycloakAuthenticationOptions
{
// App-Specific Settings
ClientId = "demo-app", // *Required*
ClientSecret = "ssshhhh", // If using public authentication, delete this line
VirtualDirectory = "", // Set this if you use a virtual directory when deploying to IIS
// Instance-Specific Settings
Realm = "PruebaRealm", // Don't change this unless told to do so
KeycloakUrl = "http://example.com/auth", // Enter your Keycloak URL here
// Template-Specific Settings
EnableWebApiMode = true,
AuthenticationType = "KeycloakOwinAuthenticationSample_keycloak_auth", // Unique identifier for the auth middleware
});
And I've added the authorization attribute to the htttp method:
[Authorize]
public IHttpActionResult Get()
{
PrivilegeProvider pPrivileges = new PrivilegeProvider();
var searchResults = pPrivileges.GetPrivilege();
if (searchResults == null)
return NotFound();
return Ok(searchResults);
}
When I perform the request including the authorization header
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: es-ES,es;q=0.9
Authorization: bearer thisisanexamplethisisanexamplethisisanexample....
Cache-Control: no-cache
Connection: keep-alive
Host: localhost:49729
Origin: http://localhost:4200
Pragma: no-cache
Referer: http://localhost:4200/privilegios
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36
I always receive:
Access Unauthorized: Requires valid bearer token authorization header
Can anyone give me an advice to achieve this goal?
I'm thinking about creating my own adapter using owin.
Thanks in advance.
I am converting an old ASP.NET web forms site to ASP.NET MVC 5. I would like to issue permanent redirects for the old page URLs.
Here is what I have done -
RouteConfig.cs:
routes.MapRoute("About_old",
"About/About.aspx",
new { controller = "Home", action = "About_old" });
HomeController.cs:
public ActionResult About_old()
{
return new RedirectResult("/About", true);
// I've also tried
// return RedirectToActionPermanent("About");
// return RedirectPermanent("/About");
}
All attempts load the correct /About view, however the URL does not change, and I do not see a 301 code in the response. In other words, the URL is "localhost/About/About.aspx" and I expect it to be "localhost/About"
Complete Request/Repsonse from Chrome:
Request URL:http://localhost:55774/About/About.aspx
Request Method:GET
Status Code:200 OK
Request Headers
GET /About/About.aspx HTTP/1.1
Host: localhost:55774
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Response Headers
Cache-Control:private
Content-Encoding:gzip
Content-Length:2284
Content-Type:text/html; charset=utf-8
Date:Sat, 01 Mar 2014 18:10:41 GMT
Server:Microsoft-IIS/8.0
Vary:Accept-Encoding
X-AspNet-Version:4.0.30319
X-AspNetMvc-Version:5.1
X-Powered-By:ASP.NET
Nowhere do I see a 301 and the URL does not change. I have to think this has to do with how I am mapping the route of the old aspx page in RouteConfig.cs as all action methods have the same results. NOTE I have put a solution using global.asax below, however I would prefer it to work as I am attempting above, so I have not accepted my answer.
Am I doing something wrong or just missing something? How do I get the 301 to issue and URL to change?
Here is my solution (Global.asax)
protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
string currentUrl = HttpContext.Current.Request.Path.ToLower();
if (currentUrl.EndsWith("/about/about.aspx"))
{
Response.Status = "301 Moved Permanently";
Response.AddHeader("Location", "/About");
Response.End();
}
}
From answer here: Global 301 redirection from domain to www.domain
I'm using RestSharp to call an external API.
This works:
var client = new RestClient(apiUrl);
var request = new RestRequest(myurl, Method.GET);
foreach (var param in parameters)
{
request.AddQueryParameter(param.Key, param.Value);
}
var response = client.Execute(request);
This doesn't:
var client = new RestClient(apiUrl);
var request = new RestRequest(myurl, Method.GET);
foreach (var param in parameters)
{
request.AddParameter(param.Key, param.Value);
}
var response = client.Execute(request);
Resulting in:
System.Exception: API Call MyWebAPIMethod GET: Failed with status code
0 - Unable to connect to the remote server
What's the difference between AddParameter and AddQueryParameter?
According to the documentation they should function the same when using HttpGET and according to Fiddler they seem to generate the same URL as well.
To answer your question
AddQueryParameter adds a parameter in the query string as ParameterType.QueryString whereas AddParameter(string, object) adds the parameter as ParameterType.GetOrPost
For more details on each parameter type, see:
GetOrPost: https://github.com/restsharp/RestSharp/wiki/ParameterTypes-for-RestRequest#getorpost
QueryString: https://github.com/restsharp/RestSharp/wiki/ParameterTypes-for-RestRequest#querystring
To solve your problem
It seems it is unrelated to the type of parameter, because the exception thrown seems to indicate you aren't even connecting to the remote server.
make sure you pass the same apiUrl / myUrl in both cases.
To answer the OP and anyone else who might have trouble with the concept.
It took me awhile to get around to the concept.
You probably need to visualise the RESTful standard of how to construct a GET request message in a url against constructing for a POST request message.
You will notice that for GET , the parameter(s) are attached to the URL header
whereas for the POST , the parameter(s) are placed in the body of the message.
RestSharp 's method AddQueryParameter() will only add the (Query) parameters in the header of the message, whereas the AddParameter() will only add the parameters to the mesasge body. As demonstrated below the GET has one query parameter with a value of "Flavours" . For the POST , the parameters contact_name and company_name are located in the bottom of the message body.
Eg:
GET message format :
GET http://www.consumerdiarydemo.cbrnetwork.test.au/api/ConsumerDiary/getSizesOrFlavours/Flavours HTTP/1.1
Host: www.consumerdiarydemo.cbrnetwork.test.au
Connection: keep-alive
Accept: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
Referer: http://www.consumerdiarydemo.cbrnetwork.test.au/ConsumerDiaryPage2template
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
POST message format :
POST
http://localhost:1234567/api/customers HTTP/1.1
Accept: application/json, text/javascript, /; q=0.01
X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
{"contact_name":"value_data1","company_name":"value_data2"}
AddParameter / Get or Post
GetOrPost behaves differently based on the method. If you execute a GET call, RestSharp will append the parameters to the Url in the form url?name1=value1&name2=value2.
On a POST or PUT Requests, it depends on whether you have files attached to a Request. If not, the Parameters will be sent as the body of the request in the form name1=value1&name2=value2.
Ref: https://restsharp.dev/usage.html#get-or-post
AddQueryParameter / Query String
QueryString works like GetOrPost, except that it always appends the parameters to the url in the form url?name1=value1&name2=value2, regardless of the request method.
Ref: https://restsharp.dev/usage.html#query-string
I have an Angular application that I'm trying to authenticate with Basic authentication to my REST service. I'm adding the authorization header with the corresponding "Base {username:password}" encoded in base64 and I'm calling my rest api but keep getting back a 401. I'm obviously missing a step here...
Here's the angular code:
angular
.module('myApp.services')
.factory('AuthenticationService', ['$http', '$q', '$location', 'Base64', 'SessionService', function ($http, $q, $location, encoder, session) {
return {
authenticate:
function (user, password) {
var deferred = $q.defer();
var url = "http://localhost:28924/api/login";
if (user && password) {
var encoded = encoder.encode(user + ':' + password);
$http.defaults.headers.common.Authorization = 'Basic ' + encoded;
console.log('here');
sessionStorage.setItem('Authorization', $http.defaults.headers.common.Authorization);
$http.get(url)
.success(function (data, status, headers, config) {
console.log('login Successful in Authentication service');
deferred.resolve(data);
session.setSession();
})
.error(function (data, status, headers, config) {
deferred.reject(status);
});
}
else {
deferred.reject(401);
}
return deferred.promise;
},
logout:
function () {
$http.defaults.headers.common.Authorization = null;
$http.defaults.headers.common.Impersonate = null;
$location.url('/login');
}
};
}
]
);
Here's my LoginController:
public class LoginController : ApiController
{
public LoginController()
{
}
[Authorize]
public HttpResponseMessage Get()
{
//return this.ControllerContext.Request.CreateResponse(HttpStatusCode.OK);
if (this.User.Identity.IsAuthenticated)
{
return this.ControllerContext.Request.CreateResponse(HttpStatusCode.OK);
}
return this.ControllerContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
And I've also set my IISExpress config file to "Allow" basic authentication, like described here: set basic authentication in IIS Express.
Does just adding the "Authorize" attribute to my Get method let the host know to check the credentials that were passed in the Authorization header? Or do I need to implement something extra, like in this SO post (the question part): basic authentication with custom message handler?
It seems to me that there needs to be more in my "Get" method, but I can't find any good examples to help walk me through this....(if you haven't figure it out, this is my first try with basic authentication AND REST api/services.
Edit: Aha! I'm getting closer. #Chandermani's response for some reason prompted me to check out my OWN web.config and I realized I didn't have this:
<security>
<authentication>
<basicAuthentication enabled="true"/>
</authentication>
</security>
Adding this now prompts me for my credentials when navigating to the .../api/login page.
Edit2: Checking to see what was sent across the wire via Chrome Dev tools shows that the authorization header is being sent, while specifying Basic authentication and my base64 encoded username and password. Here's what it looks like:
Request URL:http://localhost:28924/api/login
Request Method:GET
Status Code:401 Unauthorized
Request Headersview source
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Authorization:Basic YWblahblahblahDE=
Connection:keep-alive
Host:localhost:28924
Referer:http://localhost:28924/index.html
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
Response Headersview source
Cache-Control:private
Content-Length:6333
Content-Type:text/html; charset=utf-8
Date:Wed, 08 Jan 2014 14:32:14 GMT
Server:Microsoft-IIS/8.0
WWW-Authenticate:Negotiate
WWW-Authenticate:NTLM
WWW-Authenticate:Basic realm="localhost"
X-Powered-By:ASP.NET
X-SourceFiles:=?UTF-8?B?QzpcU291cmNlQ29kZVxUcmlhbHNNb2JpbGVcVHJhaWxzTW9iaWxlLldlYlxhcGlcbG9naW4=?=
When the api/login page prompts me for my credentials it gets stuck in an infinite loop. I'll enter them, hit enter, then it asks for them again.
Edit3: Ok, I'm able to authenticate!!! I had to specify the domain in front of my username since I'm running localhost...
Thanks everyone for pointing me in the right direction!
A combination of changes led to fixing my problem:
1) I needed to allow basic authentication in my OWN web.config. I had only previously set it up in the IISExpress applicationhost.config (see Edit(1))
2) Showing the output from Chrome Dev tools proved that the Basic Authentication authorization header was, in fact, being sent across.
3) The last problem was since I'm running on localhost, I needed to specify the domain to authenticate against in my username. There's probably a cleaner way of doing this, but I just entered this in my username field: mydomain\ganders and it started working.
I'd like my application to query a csv file from a secure website. I have no experience with web programming so I'd appreciate detailed instructions. Currently I have the user login to the site, manually query the csv, and have my application load the file locally. I'd like to automate this by having the user enter his login information, authenticating him on the website, and querying the data. The application is written in C# .NET.
I've tested the following code already and am able to access the file once the user has already authenticated himself and created a manual query.
System.Net.WebClient Client = new WebClient();
Stream strm = Client.OpenRead("https://<URL>/file.csv");
Here is the request sent to the site for authentication. I've angle bracketed the real userid and password.
POST /pwdVal.asp HTTP/1.1
Accept: image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-shockwave-flash, */*
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; Tablet PC 2.0; OfficeLiveConnector.1.4; OfficeLivePatch.1.3; .NET4.0C; .NET4.0E)
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Cookie: ASPSESSIONID<unsure if this data contained password info so removed>; ClientId=<username>
Host: www3.emidas.com
Content-Length: 36
Connection: Keep-Alive
Cache-Control: no-cache
Accept-Language: en-US
client_id=<username>&password=<password>
Most likely the server sends a cookie once login is performed. You need to submit the same values as the login form. (this can be done using UploadValues()) However, you need to save the resulting cookies in a CookieContainer.
When I did this, I did it using HttpWebRequest, however per http://couldbedone.blogspot.com/2007/08/webclient-handling-cookies.html you can subclass WebClient and override the GetWebRequest() method to make it support cookies.
Oh, also, I found it useful to use Fiddler while manually accessing the web site to see what actually gets sent back and forth to the web site, so I knew what I was trying to reproduce.
edit, elaboration requested: I can only elaborate how to do it using HttpWebRequest, I have not done it using WebClient. Below is the code snippet I used for login.
private CookieContainer _jar = new CookieContainer();
private string _password;
private string _userid;
private string _url;
private string _userAgent;
...
string responseData;
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(_url);
webRequest.CookieContainer = _jar;
webRequest.Method = "POST";
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.UserAgent = _userAgent;
string requestBody = String.Format(
"client_id={0}&password={1}", _userid, _password);
try
{
using (StreamWriter requestWriter = new StreamWriter(webRequest.GetRequestStream()))
{
requestWriter.Write(requestBody);
requestWriter.Close();
using (HttpWebResponse res = (HttpWebResponse)webRequest.GetResponse())
{
using (StreamReader responseReader = new StreamReader(res.GetResponseStream()))
{
responseData = responseReader.ReadToEnd();
responseReader.Close();
if (res.StatusCode != HttpStatusCode.OK)
throw new WebException("Logon failed", null, WebExceptionStatus.Success, res);
}
}
}
Before you go down this rabbit hole, contact the web site and ask them if they provide a web service to query user account info from. The simulated login method you are proposing should be a last resort only.
Another way you can do it is to automate IE, e.g. use a WebBrowser control. That will more accurately simulate all the clever stuff that IE does like running Javascript, which might be necessary. Although if Javascript or other clever stuff isn't necessary then using IE is a little heavy-handed and possibly prone to other problems.