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");
Related
I post a request with HttpClient:
handler = new HttpClientHandler
{
Proxy = null,
UseProxy = false,
CookieContainer = cookieContainer,
AllowAutoRedirect = allowAutoRedirect
};
client = new HttpClient(handler)
{
BaseAddress = new Uri($"http://www.example.com")
};
var sendTask = client1.SendAsync(request);
I can not read the cookie in the cookieContainer it is empty;
how to get the cookie?
I see there is an answer in other question that he can get the cookie with the handler's cookieContainer but mine is empty.
I am sure there is a cookie responsed, how to do it?
finally I fount the reason that there is no cookie.
I have to initial a global cookie and global handler and only new it once then use every where then it will work.
My team has an ASP.NET MVC-based website and WebAPI that is protected by passive federated authentication. It all works properly. The problem we're having is that we need to test the website and the web API after an automated deployment. How can we authenticate and get the FEDAUTH cookie to the website from automated test code, assuming that the test code is run by a user authorized to access the website?
You can have your Web API support active authentication. It requires some work to change the configuration and authentication handler, but it will make your web API easily accessible from a program client as well. If you just want to get a FEDAUTH cookie in your automated test code, the following code sample can work. It mimics a browser to post the user token to the website and get a cookie back.
// The code needs the STS server and the website url
var stsUrl = "https://your_STS";
var serviceUrl = "https://your_Service";
// Use Windows Credential to get the token
var binding = new WSHttpBinding(SecurityMode.Transport);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
var factory = new WSTrustChannelFactory(binding, stsUrl) { TrustVersion = TrustVersion.WSTrust13 };
// Override current login user credential if needed:
// factory.Credentials.Windows.ClientCredential = userCredential;
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Bearer,
AppliesTo = new EndpointReference(serviceUrl)
};
RequestSecurityTokenResponse rstr;
var token = factory.CreateChannel().Issue(rst, out rstr);
var fedSerializer = new System.IdentityModel.Services.WSFederationSerializer();
var rstrContent = fedSerializer.GetResponseAsString(rstr, new WSTrustSerializationContext());
// After this the security token is acquired and saved in rstrContent
var client = new HttpClient();
// Initiate a request to the service, which will be redirected to STS. Read WS fed fields from redirected URL.
var response = client.GetAsync(serviceUrl).Result;
response.EnsureSuccessStatusCode();
var redirectQuery = response.RequestMessage.RequestUri.Query;
var queryParams = System.Web.HttpUtility.ParseQueryString(redirectQuery);
// construct a authentication form
var formData = new Dictionary<string, string>
{
{"wa", queryParams["wa"]},
{"wresult", rstrContent},
{"wctx", queryParams["wctx"] },
};
// post the authentication form to the website.
response = client.PostAsync(serviceUrl, new FormUrlEncodedContent(formData)).Result;
response.EnsureSuccessStatusCode();
// After this, the auth cookie is set in this HttpClient that you can use to access your service
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 am trying to authenticate inside integration test by calling FormsAuthentication.SetAuthCookie("someUser", false);
After that I do need to call WebAPI and not receive unauthorized exception because I have authorized attribute applied.
I am using this code to create auth cookie :
var cookie = FormsAuthentication.GetAuthCookie(name, rememberMe);
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration,
ticket.IsPersistent, userData.ToJson(), ticket.CookiePath);
var encTicket = FormsAuthentication.Encrypt(newTicket);
/// Use existing cookie. Could create new one but would have to copy settings over...
cookie.Value = encTicket;
Now I want to add this cookie to HttpRequestMessage inside new HttpClient and send this with my regular request in integration test.
I don't know how to add that auth cookie to HttpRequestMessage ?
For manipulating cookies, you need to use WebRequestHandler along with HttpClient. For example,
var handler = new WebRequestHandler();
var client = new HttpClient(handler);
// use handler to configure request such as add cookies to send to server
CookieContainer property will allow to access cookies collection.
On different note, I doubt if creating FormsAuthentication cookie on client will work. A same encryption key would be needed on both client/server. The best approach would be to replay the login request for actual web API - most probably, it would be a POST to login page with user credentials. Observe the same over browser using tool such as Fiddler and construct the same request within your http client.
Almost 6 years late, but still may be helpful. The solution based on this one:
https://blogs.taiga.nl/martijn/2016/03/10/asp-net-web-api-owin-authenticated-integration-tests-without-authorization-server/
First, while creating Owin TestServer you have to create DataProtector:
private readonly TestServer _testServer;
public IDataProtector DataProtector { get; private set; }
public Server(OwinStartup startupConfig)
{
_testServer = TestServer.Create(builder =>
{
DataProtector = builder.CreateDataProtector(
typeof(CookieAuthenticationMiddleware).FullName, DefaultAuthenticationTypes.ApplicationCookie, "v1");
startupConfig.Configuration(builder);
});
}
Then generate cookie like this (use DataProtector created in previous step):
public string GeterateCookie()
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Role, "your-role"),
new Claim(ClaimTypes.UserData, "user-data"),
new Claim(ClaimTypes.Name, "your-name")
};
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie, ClaimTypes.Name, ClaimTypes.Role);
var tdf = new TicketDataFormat(DataProtector);
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties {ExpiresUtc = DateTime.UtcNow.AddHours(1)});
var protectedCookieValue = tdf.Protect(ticket);
var cookie = new CookieHeaderValue("yourCookie", protectedCookieValue)
{
Path = "/",
HttpOnly = true
};
return cookie.ToString();
}
Make sure to set required claims, initialize ClaimsIdentity according to settings provided to UseCookieAuthentication method, and setting correct CookieName.
The last step is to add CookieHeader to your request:
public Task<HttpResponseMessage> RequestAsync(HttpRequestMessage request)
{
request.Headers.Add("cookie", GenerateCookie());
return _client.SendAsync(request);
}
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.