I have a WebForms application which expose some WebAPI methods. The application uses standard Forms Authentication. I'm able to call methods from browser when I properly authenticated.
Now I want to call the API from c# application using HttpClient, but always receiving error 403 "Forbidden".
I can see that cookies container contains SessionId cookie.
The code:
var cookieContainer = new CookieContainer();
// authenticate
{
var webRequestHandler = new WebRequestHandler();
webRequestHandler.CookieContainer = cookieContainer;
webRequestHandler.UseCookies = true;
var client = new HttpClient(webRequestHandler);
client.BaseAddress = new Uri("https://localhost/<WEBFORMSAPP>/");
string unid = "<USERNAME$FIELD$NAME>";
string pwdid = "<PASSWORD$FIELD$NAME>";
HttpContent loginData = new FormUrlEncodedContent(
new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(unid, "<USERNAME>"),
new KeyValuePair<string, string>(pwdid, "<PASSWORD>"),
}
);
System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
var postResult = client.PostAsync("<LOGINPAGE.aspx>", loginData);
postResult.Wait();
postResult.Result.EnsureSuccessStatusCode();
}
// call web api
{
var webRequestHandler = new WebRequestHandler();
webRequestHandler.CookieContainer = cookieContainer;
webRequestHandler.UseCookies = true;
var client = new HttpClient(webRequestHandler);
client.BaseAddress = new Uri("https://localhost/<WEBFORMSAPP>/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
var r = client.GetAsync("api/<SOME_METHOD>");
r.Wait();
r.Result.EnsureSuccessStatusCode(); // Fails with 403 error
}
To build the proper web request we need to provide server with the correct __VIEWSTATE value and the name of the button which cause the postback (in addition to the login/password pair).
To do this first we need to make a GET request to the login page and extract then __VIEWSTATE value.
I have used code from this blog post:
http://odetocode.com/articles/162.aspx
The code to create authorization cookies:
public CookieContainer CreateAuthorizationCookies(string login, string password)
{
var result = new CookieContainer();
System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
var webRequestHandler = new WebRequestHandler();
webRequestHandler.CookieContainer = result;
webRequestHandler.UseCookies = true;
var client = new HttpClient(webRequestHandler);
client.BaseAddress = new Uri("https://*********/");
// get viewstate
string viewstate;
{
var r = client.GetStringAsync("***/LoginPage.aspx");
r.Wait();
// see http://odetocode.com/articles/162.aspx
viewstate = ExtractViewState(r.Result);
}
HttpContent loginData = new FormUrlEncodedContent(
new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("__VIEWSTATE", viewstate),
new KeyValuePair<string, string>("***$UserName", login),
new KeyValuePair<string, string>("***$Password", password),
new KeyValuePair<string, string>("***$LoginButton", "Log In"), // "value" of the HTML input element
}
);
var postResult = client.PostAsync("****/LoginPage.aspx", loginData);
postResult.Wait();
postResult.Result.EnsureSuccessStatusCode();
return result;
}
After that we can use created CookieContainer in the further requests.
Related
I'm experiencing an intermittent problem with our SharePoint 2010 REST API. I have a .Net Core Console application that makes a series of calls to SharePoint List Endpoints to get a JSON response. My problem is that at random times, the API response is an error page:
A relative URI cannot be created because the 'uriString' parameter
represents an absolute URI.http://www.example.com/somefolder/file.svc
Is there a problem with my HTTPClient configuration? Is there a configuration setting that I can toggle in SharePoint to prevent the error or more reliable?
var uri = new Uri("http://www.example.com/");
var credential = new NetworkCredential("username", "password", "domain");
var credentialsCache = new CredentialCache { { uri, "NTLM", credential } };
var handler = new HttpClientHandler { Credentials = credentialsCache };
HttpClient Client = new HttpClient(handler);
Client.BaseAddress = new Uri("http://www.example.com/sharepoint/path/ListData.svc/");
// Make the list request
var result = await Client.GetAsync("MySharePointList");
To get the list items, the REST API URI like below.
http://sp2010/_vti_bin/ListData.svc/listname
Modify the code as below.
var siteUrl = "http://www.example.com/";
var listName = "MySharePointList";
var uri = new Uri(siteUrl);
var credential = new NetworkCredential("username", "password", "domain");
var credentialsCache = new CredentialCache { { uri, "NTLM", credential } };
var handler = new HttpClientHandler { Credentials = credentialsCache };
HttpClient client = new HttpClient(handler);
client.BaseAddress = new Uri(uri, "/_vti_bin/ListData.svc");
client.DefaultRequestHeaders.Add("Accept", "application/json;odata=verbose");
client.DefaultRequestHeaders.Add("ContentType", "application/json;odata=verbose");
var requestURL = siteUrl + "/_vti_bin/ListData.svc/" + listName;
// Make the list request
var result = client.GetAsync(requestURL).Result;
var items= result.Content.ReadAsStringAsync();
I've implemented Twilio REST API with C# successfully earlier. However, all of a sudden the API calls that are made keep getting 400 - BAD REQUEST.
The response body doesn't contain any specific error either...
I'll say it again, it worked for a week or two and all of sudden it returns BAD REQUEST.
The code is exact the following below.
public async Task SendSmsAsync(string number, string message)
{
var accountSid = _configuration["Authentication:Twilio:AccountSID"];
var authToken = _configuration["Authentication:Twilio:AuthToken"];
var twilioNumber = _configuration["Authentication:Twilio:Number"];
var credentials = new NetworkCredential(accountSid, authToken);
var handler = new HttpClientHandler { Credentials = credentials };
using (var client = new HttpClient(handler))
{
var url = $"https://api.twilio.com/2010-04-01/Accounts/{ accountSid }/Messages";
var body = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("To", number),
new KeyValuePair<string, string>("From", twilioNumber),
new KeyValuePair<string, string>("Body", message),
};
var content = new FormUrlEncodedContent(body);
content.Headers.ContentType.CharSet = "UTF-8";
var response = await client.PostAsync(url, content);
if (response.IsSuccessStatusCode)
{
//Uri success = response.Headers.Location;
}
}
}
I have problem to redirect to other page, for example, while i am keeping Credential Cache. I'm using:
cCache.Add(new Uri("http://mypage.com"), "Basic", new NetworkCredential("admin", "admin"));
and with:
using (var client = new HttpClient(new HttpClientHandler { Credentials = cCache }))
{
var request = new System.Net.Http.HttpRequestMessage()
{
RequestUri = new Uri("http://mypage.com"),
Method = HttpMethod.Get,
};
var result = new HttpResponseMessage();
var requestTask = client.SendAsync(request).ContinueWith((argRequestTask) =>
{
result = argRequestTask.Result;
});
requestTask.Wait();
}
status code that i get is 200 OK, and that is good. Result that i get is content of that page and that is good to, but im still on same page. If i redirect with:
Response.Redirect("http://mypage.com");
i lose my credentials. How i can solve this? tnx
My code is showing the the following error: "Error: 401 Not Authorized" when I post the data.
My class:
public class APICommands : IDisposable
{
public APICommands()
{
this.HttpClientHandler = new HttpClientHandler();
// Set authentication.
this.HttpClientHandler.UseDefaultCredentials = false;
this.HttpClientHandler.Credentials = new NetworkCredential("username#myemail.com", "mypassword");
this.HttpClient = new HttpClient(this.HttpClientHandler);
this.HttpClient.BaseAddress = new Uri("https://api.myhost.com");
this.HttpClient.DefaultRequestHeaders.Accept.Clear();
this.HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
private HttpClient HttpClient { get; set; }
private HttpClientHandler HttpClientHandler { get; set; }
public async Task<JsonResultBoleto> CreateClient(string name, string age)
{
ServicePointManager.Expect100Continue = false;
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("name", name));
postData.Add(new KeyValuePair<string, string>("age", age));
HttpContent content = new FormUrlEncodedContent(postData);
// When I call this method "PostAsync", the error message is displayed.
HttpResponseMessage response = await this.HttpClient.PostAsync("https://api.myhost.com/client/", content);
if (response.IsSuccessStatusCode)
{
// Do something.
}
return null;
}
}
The error started when I added this code: ServicePointManager.Expect100Continue = false;. I added this code for resolve another error: "417 - Expectation Failed" :(
What you're going?
Thanks...
It appears that the authentication mechanism is not responding to the 401 - Unauthorized response. You can add the PreAuthenticate setting to the HttpClientHandler to force the sending of credentials during the initial request instead of waiting for an authorization challenge.
...
// Set authentication.
this.HttpClientHandler.UseDefaultCredentials = false;
this.HttpClientHandler.Credentials = new NetworkCredential("username#myemail.com", "mypassword");
this.HttpClientHandler.PreAuthenticate = true;
I think that ServicePointManage.Expect100Continue is not needed. I am assuming that credential not work.
Why not try it via authorization headers:
string authInfo = "username#myemail.com:mypassword";
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authInfo);
I've got the following code that works successfully. I can't figure out how to get the cookie out of the response. My goal is that I want to be able to set cookies in the request and get cookies out of the response. Thoughts?
private async Task<string> Login(string username, string password)
{
try
{
string url = "http://app.agelessemail.com/account/login/";
Uri address = new Uri(url);
var postData = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("username", username),
new KeyValuePair<string, string>("password ", password)
};
HttpContent content = new FormUrlEncodedContent(postData);
var cookieJar = new CookieContainer();
var handler = new HttpClientHandler
{
CookieContainer = cookieJar,
UseCookies = true,
UseDefaultCredentials = false
};
var client = new HttpClient(handler)
{
BaseAddress = address
};
HttpResponseMessage response = await client.PostAsync(url,content);
response.EnsureSuccessStatusCode();
string body = await response.Content.ReadAsStringAsync();
return body;
}
catch (Exception e)
{
return e.ToString();
}
}
Here is the complete answer:
HttpResponseMessage response = await client.PostAsync(url,content);
response.EnsureSuccessStatusCode();
Uri uri = new Uri(UrlBase);
var responseCookies = cookieJar.GetCookies(uri);
foreach (Cookie cookie in responseCookies)
{
string cookieName = cookie.Name;
string cookieValue = cookie.Value;
}
To add cookies to a request, populate the cookie container before the request with CookieContainer.Add(uri, cookie). After the request is made the cookie container will automatically be populated with all the cookies from the response. You can then call GetCookies() to retreive them.
CookieContainer cookies = new CookieContainer();
HttpClientHandler handler = new HttpClientHandler();
handler.CookieContainer = cookies;
HttpClient client = new HttpClient(handler);
HttpResponseMessage response = client.GetAsync("http://google.com").Result;
Uri uri = new Uri("http://google.com");
IEnumerable<Cookie> responseCookies = cookies.GetCookies(uri).Cast<Cookie>();
foreach (Cookie cookie in responseCookies)
Console.WriteLine(cookie.Name + ": " + cookie.Value);
Console.ReadLine();
There's alternative if you don't have access to the HttpClient and can't inject the CookieContainer. This works in .NET Core 2.2:
private string GetCookie(HttpResponseMessage message)
{
message.Headers.TryGetValues("Set-Cookie", out var setCookie);
var setCookieString = setCookie.Single();
var cookieTokens = setCookieString.Split(';');
var firstCookie = cookieTokens.FirstOrDefault();
var keyValueTokens = firstCookie.Split('=');
var valueString = keyValueTokens[1];
var cookieValue = HttpUtility.UrlDecode(valueString);
return cookieValue;
}
You can easily get a cookie value with the given URL.
private async Task<string> GetCookieValue(string url, string cookieName)
{
var cookieContainer = new CookieContainer();
var uri = new Uri(url);
using (var httpClientHandler = new HttpClientHandler
{
CookieContainer = cookieContainer
})
{
using (var httpClient = new HttpClient(httpClientHandler))
{
await httpClient.GetAsync(uri);
var cookie = cookieContainer.GetCookies(uri).Cast<Cookie>().FirstOrDefault(x => x.Name == cookieName);
return cookie?.Value;
}
}
}
Not in every case you can add httpClientHandler to httpClient. For example, when you use integration tests testServer.CreateClient() or inject httpClient from IHttpClientFactory. So, I have simply read values from header.
public static List<Cookie> GetCookies(this HttpResponseMessage message)
{
message.Headers.TryGetValues("Set-Cookie", out var cookiesHeader);
var cookies = cookiesHeader.Select(cookieString => CreateCookie(cookieString)).ToList();
return cookies;
}
private static Cookie CreateCookie(string cookieString)
{
var properties = cookieString.Split(';', StringSplitOptions.TrimEntries);
var name = properties[0].Split("=")[0];
var value = properties[0].Split("=")[1];
var path = properties[2].Replace("path=", "");
var cookie = new Cookie(name, value, path)
{
Secure = properties.Contains("secure"),
HttpOnly = properties.Contains("httponly"),
Expires = DateTime.Parse(properties[1].Replace("expires=", ""))
};
return cookie;
}
CreateCookie method may be modified to exactly match your cookie properties.