I try to use the new .net 4.5 HttpClient from System.net.http.
I set-up my client like this
CookieContainer cookieJar = new CookieContainer();
HttpClientHandler handler = new HttpClientHandler
{
CookieContainer = cookieJar,
AllowAutoRedirect = true
};
handler.UseCookies = true;
handler.UseDefaultCredentials = false;
HttpClient client = new HttpClient(handler as HttpMessageHandler);
then I do a client.GetAsync(url)
now I am inspecting the response and try to get the cookie / session values for a following post.
I try to test a login scenario of an existing page via code...
How do I get the cookie information in a response? Or do I walk on a wrong path here?
Any explanation would be fantastic...
The cookie exists in the form of a header that is the instruction to create the cookie on the client. Those headers have the form of "Set-Cookie" as the actual header, with the value of "CookieTitle={form encoded value}". Getting that cookie looks like this:
var cookieTitle = "MyCookieTitle";
var response = ... // get response object
var cookie = response.Headers.GetValues("Set-Cookie").First(x => x.StartsWith(cookieTitle));
That gets you the raw string of the header, which will look like this:
CookieTitle=this+is+the+value+of+the+cookie
You can check the 'Set-Cookie' header of the HTTP response.
You don't need to get the cookies and re-specify them for your next POST. The HttpClient will take care of doing that for you.
As long as the URL that you are POSTing to is within the path that was defined by the cookie then the cookie will automatically be resent with the request.
For example, if you create your cookie like this,
public class CookieController : ApiController
{
public HttpResponseMessage Get() {
var httpResponseMessage = new HttpResponseMessage();
var cookieHeaderValues = new List<CookieHeaderValue>();
cookieHeaderValues.Add(new CookieHeaderValue("foo","bar")
{Domain = "myhost",Path = "/"});
httpResponseMessage.Headers.AddCookies(cookieHeaderValues);
return httpResponseMessage;
}
}
Any request to any URL below http://myhost/ will automatically be sent this cookie.
Related
I'd like to make an http request to a remote server while properly handling cookies (eg. storing cookies sent by the server, and sending those cookies when I make subsequent requests). It'd be nice to preserve any and all cookies
The built in Windows.Web.Http.HttpClient can manage cookies if you pass in an instance of HttpBaseProtocolFilter to its constructor. This class then has a CookieManager property which contains the cookies and you can even modify it and add your custom cookies for next requests.
However, you should instead reference the System.Net.Http NuGet package and use its HttpClient which always is up-to-date and regularly updated. In this case the HttpClient class accepts a HttpClientHandler instance in its constructor and this class in turn has the CookieContainer property which works in a similar manner as CookieManager in Windows.Web.Http.
System.Net.Http.HttpClient
Send custom cookie
var handler = new HttpClientHandler()
{
CookieContainer = new System.Net.CookieContainer(),
UseCookies = true
};
var client = new System.Net.Http.HttpClient(handler);
handler.CookieContainer.Add(targetUri, new System.Net.Cookie("name", "value"));
var response = await client.GetAsync(targetUri);
Retrieve cookie
var cookie = handler.CookieContainer["name"];
Windows.Web.Http.HttpClient
Send custom cookie
var filter = new Windows.Web.Http.Filters.HttpBaseProtocolFilter()
{
CookieUsageBehavior = Windows.Web.Http.Filters.HttpCookieUsageBehavior.Default
};
filter.CookieManager.SetCookie(new Windows.Web.Http.HttpCookie("name", "domain", "path")
{
Value = "value"
});
var client = new Windows.Web.Http.HttpClient(filter);
var response = await client.GetAsync(targetUri);
Retrieve a cookie
var cookie = filter.CookieManager.GetCookies(targetUri).
FirstOrDefault(cookie => cookie.Name == "name");
I want to modify the response body from the token endpoint response.
I've tried to intercept the /Token request with a MessageHandler but it doesn't work.
I'm able to add some additional informations to the response by overriding the OAuthAuthorizationServerProvider.TokenEndpointmethod, but I'm not able to create my own response body.
Is there a way to intercept the /Token request?
Edit
I found out how to remove the response body content from the token endpoint response, like this: HttpContext.Current.Response.SuppressContent = true;
It seems the right way to achieve my goal, but now when I use the context.AdditionalResponseParameters.Add() method to add my custom information, the SuppressContent block any alterations.
Now I have something like this:
// Removing the body from the token endpoint response
HttpContext.Current.Response.SuppressContent = true;
// Add custom informations
context.AdditionalResponseParameters.Add("a", "test");
To simply add new items to the JSON token response, you can use TokenEndpointResponse instead of the TokenEndpoint notification.
If you're looking for a way to completely replace the token response prepared by the OAuth2 authorization server by your own one, there's sadly no easy way to do that because OAuthAuthorizationServerHandler.InvokeTokenEndpointAsync doesn't check the OAuthTokenEndpointContext.IsRequestCompleted property after invoking the TokenEndpointResponse notification.
https://github.com/aspnet/AspNetKatana/blob/dev/src/Microsoft.Owin.Security.OAuth/OAuthAuthorizationServerHandler.cs
This is a known issue, but it was too late to include it in Katana 3 when I suggested to fix it.
You should give Owin.Security.OpenIdConnect.Server a try: it's an a fork of the OAuthAuthorizationServerMiddleware designed for Katana 3.0 and 4.0.
https://www.nuget.org/packages/Owin.Security.OpenIdConnect.Server/1.0.2
Of course, it includes the correct check to allow bypassing the default token request processing (this was even one of the first things I fixed when forking it).
You were almost there +Samoji #Samoji and really helped/inspired me to get the answer.
// Add custom informations
context.AdditionalResponseParameters.Add("a", "test");
// Overwrite the old content
var newToken = context.AccessToken;
context.AdditionalResponseParameters.Add("access_token", newToken);
I found it just replaced my old token with my new.
This question is similar to How to extend IdentityServer4 workflow to run custom code
So you can create custom middleware and register it before OAuth2 service in Startup:
public void Configuration(IAppBuilder app)
{
....
app.Use(ResponseBodyEditorMiddleware.EditResponse);
app.UseOAuthAuthorizationServer(...);
...
}
where custom middleware is:
public static async Task EditResponse(IOwinContext context, Func<Task> next)
{
// get the original body
var body = context.Response.Body;
// replace the original body with a memory stream
var buffer = new MemoryStream();
context.Response.Body = buffer;
// invoke the next middleware from the pipeline
await next.Invoke();
// get a body as string
var bodyString = Encoding.UTF8.GetString(buffer.GetBuffer());
// make some changes to the body
bodyString = $"The body has been replaced!{Environment.NewLine}Original body:{Environment.NewLine}{bodyString}";
// update the memory stream
var bytes = Encoding.UTF8.GetBytes(bodyString);
buffer.SetLength(0);
buffer.Write(bytes, 0, bytes.Length);
// replace the memory stream with updated body
buffer.Position = 0;
await buffer.CopyToAsync(body);
context.Response.Body = body;
}
The best way to intercept request and response is via MessageHandler if you want to avoid doing so after a request has reached the IControllerFactory handler in the pipeline - obviously in that case use a custom 'Attribute'
I have used MessageHandlers in the past to intercept request to api/token, create a new request and get the response, create a new response.
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
//create a new auth request
var authrequest = new HttpRequestMessage();
authrequest.RequestUri = new Uri(string.Format("{0}{1}", customBaseUriFromConfig, yourApiTokenPathFromConfig));
//copy headers from the request into the new authrequest
foreach(var header in request.Headers)
{
authrequest.Headers.Add(header.Key, header.Value);
}
//add authorization header for your SPA application's client and secret verification
//this to avoid adding client id and secret in your SPA
var authorizationHeader =
Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", _clientIdFromConfig, _secretKeyFromConfig)));
//copy content from original request
authrequest.Content = request.Content;
//add the authorization header to the client for api token
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(request.Headers.Authorization.Scheme, authorizationHeader);
var response = await client.PostAsync(authrequest.RequestUri, authrequest.Content, cancellationToken);
if(response.StatusCode == HttpStatusCode.OK)
{
response.Headers.Add("MyCustomHeader", "Value");
//modify other attributes on the response
}
return response;
}
This works for me perfectly. There is, however, the configuration for this handler required in the WebApiConfig.cs file (RouteConfig.cs if you're using ASP.NET MVC).
Can you elaborate on what it is that does not work for you on the handler?
I'm developing a ASP WebAPI (ASP MVC 4) application with a WPF (.NET 4.0) client, using Visual Studio 2012. The client needs to login to the server. I use FormsAuthentication with an authentication cookie to login. The login already works fine in ASP MVC.
The problem is that, although the login is sucessfully executed on the server and the cookie is sent back to the client, the cookie is not sent in subsequent calls to the server, even though the CookieContainer is reused with the auth cookie set.
Here is a simplified version of the code:
CLIENT
public async Task<UserProfile> Login(string userName, string password, bool rememberMe)
{
using (var handler = new HttpClientHandler() { CookieContainer = this.cookieContainer })
using (var httpClient = new HttpClient(handler))
{
httpClient.BaseAddress = new Uri("http://localhost:50000/");
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var result = await httpClient.PostAsJsonAsync("api/auth/login", new
{
username = userName,
password = password,
rememberMe = rememberMe
});
result.EnsureSuccessStatusCode();
var userProfile = await result.Content.ReadAsAsync<UserProfile>();
if (userProfile == null)
throw new UnauthorizedAccessException();
return userProfile;
}
}
public async Task<ExamSubmissionResponse> PostItem(Item item)
{
using (var handler = new HttpClientHandler() { CookieContainer = this.cookieContainer })
using (var httpClient = new HttpClient(handler))
{
httpClient.BaseAddress = new Uri("http://localhost:50000/");
var result = await httpClient.PostAsJsonAsync("api/Items/", item);
}
}
SERVER
[HttpPost]
public HttpResponseMessage Login(LoginModel model)
{
if (this.ValidateUser(model.UserName, model.Password))
{
// Get user data from database
string userData = JsonConvert.SerializeObject(userModel);
var authTicket = new FormsAuthenticationTicket(
1,
model.UserName,
DateTime.Now,
DateTime.Now.AddMinutes(10 * 15),
model.RememberMe,
userData
);
string ticket = FormsAuthentication.Encrypt(authTicket);
var cookie = new CookieHeaderValue(FormsAuthentication.FormsCookieName, ticket);
var response = Request.CreateResponse(HttpStatusCode.Created, userModel);
response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
return response;
}
return null;
}
First I debugged the problem using Fiddler2 (I used the base address as "http://localhost.fiddler:50000/" to view local traffic). Then I suspected that fiddler might be interfering, so I just debugged with Visual Studio 2012.
What I have tried and verified:
The server is reached by the Login method
The user is sucessfully authenticated with the data sent from the client
The cookie is set on the server
The cookie is in the response (verified with fiddler)
The cookie is in the CookieContainer after the operation. There is a strange thing here: the domain of the cookie in the container is set as "localhost" (verified with VS2012 debugger). Shouldn't it be "http://localhost:50000" ? When I try to get the cookies of the container using cookieContainer.GetCookies(new Uri("http://localhost:50000")) it returns nothing. When I try it using cookieContainer.GetCookies(new Uri("localhost")) it gives me an invalid Uri error. Not sure what's going on here.
The cookie is in the container just before the PostItem request is made. The container is correctly set in the HttpClient when the statement httpClient.PostAsJsonAsync is reached.
The cookie is not sent to the server (I checked it with fiddler and in the Application_PostAuthenticateRequest method in the Global.asax.cs, verifying this.Request.Cookies)
I suspect the cookie is not being sent due to a domain mismatch in the CookieContainer, but why the domain is not set as it should in the CookieContainer in the first place?
Your problem is that you are not setting any path on the cookie that you send back from your Web Api controller.
There are two things that control where cookies are sent:
The domain of the cookie
The path of the cookie
Regarding the domain, the consensus seems to be that the port number should no longer (but still might) be a factor in evaluating the cookie domain. See this question for more info about how port number affects the domain.
About the path: Cookies are associated with a specific path in their domain. In your case, the Web Api is sending a cookie without specifying it's path. By default the cookie will then be associated with the path of the request/response where the cookie was created.
In your case the cookie will have the path api/auth/login. This means the the cookie will be sent to child paths (for lack of a better term) of this path but not to parent or sibling paths.
To test this, try:
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/login")
This should give you the cookie. So should this:
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/login/foo/bar")
These on the other hand will not find the cookie:
cookieContainer.GetCookies(new Uri("http://localhost/")
cookieContainer.GetCookies(new Uri("http://localhost/api/")
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/")
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/foo")
cookieContainer.GetCookies(new Uri("http://localhost/api/Items/")
To fix the issue, simply add the path "/" (or perhaps "/api") to the cookie before sending the resonse:
...
string ticket = FormsAuthentication.Encrypt(authTicket);
var cookie = new CookieHeaderValue(FormsAuthentication.FormsCookieName, ticket);
cookie.Path = "/";
var response = Request.CreateResponse(HttpStatusCode.Created, userModel);
response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
...
I have an MVC3 application, and my controller actions are secured using the [Authorize] attribute. So far, so good, forms auth works great. Now I want to add a JSON API to my application so some actions are accessible to non-browser clients.
I'm having trouble figuring out the 'right' design.
1) Each user has secret API key.
2) User ID 5 calls http://myapp.com/foocontroller/baraction/5?param1=value1¶m2=value2&secure_hash=someValue. Here, secure_hash is simply the SHA1 hash of param1 and param2's values appended with the secret API key for the user
2) /foocontroller/baraction will be decorated with [CustomAuthorize]. This will be an implementation of AuthorizeAttribute which will check if the request is coming in as JSON. If it is, it will check the hash and see if it matches. Otherwise, if the request is HTML, then I call into existing authorization.
I am not at all sure if this will work. Is it normal to pass a secure hash in the query string or should I be passing it in as an HTTP header? Is it better to use HTTP basic auth instead of a hash made using the secret API key?
Tips from anyone who has made a web API using ASP.NET MVC would be welcome!
I pass the secret API key along with username and password in the request body. Once authorized, a token is generated and the client has to pass that in the Authorization header. This gets checked in the base controller on each request.
Client calls myapp.com/authorize which return auth token.
Client stores auth token locally.
Client calls myapp.com/anycontroller, with authtoken in Authorization header.
AuthorizeController inherits from controller.
Anycontroller inherits from a custom base controller which performs the authorization code.
My example requires the following route which directs POST requests to an ActionResult named post in any controller. I am typing this in by hand to simplify it as much as possible to give you the general idea. Don't expect to cut and paste and have it work :)
routes.MapRoute(
"post-object",
"{controller}",
new { controller = "Home", action = "post" {,
new { httpMethod = new HttpMethodConstraint("POST")}
);
Your auth controller can use this
public class AuthorizationController : Controller
{
public ActionResult Post()
{
string authBody;
var request = ControllerContext.HttpContext.Request;
var response = ControllerContext.HttpContext.Response;
using(var reader = new StreamReader(request.InputStream))
authBody = reader.ReadToEnd();
// authorize based on credentials passed in request body
var authToken = {result of your auth method}
response.Write(authToken);
}
}
Your other controllers inherit from a base controller
public class BaseController : Controller
{
protected override void Execute(RequestContext requestContext)
{
var request = requestContext.HttpContext.Request;
var response = requestContext.HttpContext.Response;
var authToken = Request.Headers["Authorization"];
// use token to authorize in your own method
var authorized = AmIAuthorized();
if(authorized = false) {
response.StatusCode = 401;
response.Write("Invalid token");
return;
}
response.StatusCode = 200; // OK
base.Execute(requestContext); // allow inheriting controller to continue
}
}
Sample code to call the api
public static void ExecutePostRequest(string contentType)
{
request = (HttpWebRequest)WebRequest.Create(Uri + Querystring);
request.Method = "POST";
request.ContentType = contentType; // application/json usually
request.Headers["Authorization"] = token;
using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
writer.Write(postRequestData);
// GetResponse reaises an exception on http status code 400
// We can pull response out of the exception and continue on our way
try
{
response = (HttpWebResponse)request.GetResponse();
}
catch (WebException ex)
{
response = (HttpWebResponse)ex.Response;
}
finally
{
using (StreamReader reader =
new StreamReader(response.GetResponseStream()))
responseText = reader.ReadToEnd();
httpcontext = HttpContext.Current;
}
}
I have logged in to the site with my webbrowser and whenever I try to call
WebClient myWebClient = new WebClient();
string str = myWebClient.DownloadString("http://localhost/myxml.aspx");
Response.Write(str.ToString());
Or
XmlTextReader reader = new XmlTextReader(url);
while (reader.Read()) {
Response.Write(reader.ReadOuterXml());
}
Response.Write returns me the login page.
Is there away to attach user SessionId to WebClient or XmlTextReader or how can I request another page in C# with current logged user?
You'll need to use an object that can handle storing cookies. In this case, you'll need the HttpWebRequest class. You'll also need a CookieContainer to manage authentication cookies.
To do this you would:
Create a CookieContainer object (a cookie jar) that you can keep track of throughout the scope of every request you make.
Create an HttpWebRequest that logs into the site you're accessing.
Use the CookieContainer you created in step 1 for every subsequent request.
Below is an example of how to use the HttpWebRequest, HttpWebResponse, and CookieContainer classes together to make a simple request that will set some cookies, and then using those cookies on a subsequent request. The rest should be easy assuming everything is well formed markup ;)
CookieContainer cookieJar = new CookieContainer();
var webRequest = (HttpWebRequest)HttpWebRequest.Create("http://www.google.com");
webRequest.CookieContainer = cookieJar;
var webResponse = webRequest.GetResponse();
using (var reader = new StreamReader(webResponse.GetResponseStream()))
{
Response.Write(reader.ReadToEnd());
}
var anotherWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://www.google.com/search?q=stackoverflow.com");
anotherWebRequest.CookieContainer = cookieJar;
webResponse = anotherWebRequest.GetResponse();
Another option (if you really want to use the WebClient class) would be to parse out the ResponseHeaders property of the class once you've made your request and include the appropriate cookies in your next request. This is a little more involved though since it requires you to manage your cookies manually.
Since I'm assuming that you want to be able to traverse your web responses as XML, I suggest you look into the open source library, HtmlAgilityPack. It allows you to send in markup from a web site that is (most likely) not well-formed, or has some sort of invalid markup in it, and then fixes the invalid parts so that you can traverse it like XML.
While doing some screen scraping, I had the same issue. I was requesting a Classic ASP app on an IIS server (I could tell by some of the headers that the server reponded with). The way I supported an ongoing session was by enabling Cookies on the WebClient. Theres no toggle for it, you have to subclass WebClient to get it to work.
public class CookieAwareWebClient : WebClient
{
protected CookieContainer _container = new CookieContainer();
public CookieContainer Cookies
{
get { return _container; }
set { _container = value; }
}
protected override WebRequest GetWebRequest(Uri address)
{
HttpWebRequest httpRequest = base.GetWebRequest(address) as HttpWebRequest;
if (httpRequest.CookieContainer != null)
{
if (httpRequest != null)
{
CookieCollection newCookies =
GetUniqueCookies(
address
,httpRequest.CookieContainer.GetCookies(address)
);
foreach (Cookie c in newCookies)
httpRequest.CookieContainer.Add(c);
}
}
else
httpRequest.CookieContainer = this.Cookies;
return (WebRequest)httpRequest;
}
Note: this isn't a unique solution, I found this out there on the web myself, but I've implemented the solution and it works really well.