I recently had the same issue when upgrading from TeamCity 10 to TeamCity 2022 and eventually managed to find out how to fix it. Importantly, I didn't have to have access the internal TeamCity properties to disable the CSRF check as I figured that it's not a bad thing to have the extra layer of security.
Things that were probably important that I changed:
I generated an access token for the account that I was using to access the REST API so I wouldn't need to put my password in the code. The access token can be associated with more restrictive permissions than your own account.
Using HttpClient
, authorisation is achieved by adding a default header (via the DefaultHeaders
property) with the name "Authorization"
and value "Bearer {apiToken}"
.
I read the /authenticationTest.html?csrf
endpoint to get a CSRF token then add that value as a default header ("X-TC-CSRF-Token"
). It's also possible to use the "tc-csrf-token"
header in the same way.
Note that because the "Authorization"
and "X-TC-CSRF-Token"
/"tc-csrf-token"
headers are added as default headers, there's no need to do this explicitly on every HTTP (GET, POST ,DELETE, etc.) method.
Here's some sample code which covers all of the above.
public class Sample
{
private readonly string _baseUrl;
private readonly HttpClient _httpClient = new HttpClient();
public Sample(string baseUrl, string accessToken)
{
_baseUrl = baseUrl;
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");
var csrfToken = get("/authenticationTest.html?csrf");
_httpClient.DefaultRequestHeaders.Add("X-TC-CSRF-Token", csrfToken);
}
public void CreateProject(string id, string name)
{
post("/app/rest/projects", $"<newProjectDescription id=\"{id}\" name=\"{name}\" />");
}
private string get(string relativeUrl)
{
var getTask = _httpClient.GetAsync(_baseUrl + relativeUrl);
getTask.Wait();
var response = getTask.Result;
response.EnsureSuccessStatusCode();
var readTask = response.Content.ReadAsStreamAsync();
readTask.Wait();
using ( var stream = readTask.Result )
using ( var reader = new StreamReader(stream) )
return reader.ReadToEnd();
}
private void post(string relativeUrl, string data)
{
using ( var content = new StringContent(data, Encoding.UTF8, "application/xml") )
{
var postTask = _httpClient.PostAsync(_baseUrl + relativeUrl, content);
postTask.Wait();
var response = postTask.Result;
response.EnsureSuccessStatusCode();
var readTask = response.Content.ReadAsStreamAsync();
readTask.Wait();
}
}
}
Call it using, e.g.
// Change these according to your situation
var baseUrl = "http://localhost:8111";
var accessToken = "1234567890";
var sample = new Sample(baseUrl, accessToken);
sample.CreateProject("ProjectID", "ProjectName");
Note that this is just a sample to get you on your feet: I've kept the code short by taking various shortcuts in the code (e.g. the HttpClient
should really be one static HttpClient for the application: see Guidelines for using HttpClient on learn.microsoft.com).