2

I am trying to create a TeamCity project from .NET Core. I have written the code below but when I am trying to run the code I get an error.

Code:

public void CreateProject(string name, string newProjectId, string parentProjectId)
{
    InvokeTeamCityApi(() => _apiClient.CreateProject(new NewProjectDescription
    {
        Name = name,
        Id = newProjectId,
        SourceProject = new GetProjectResponse
        {
            Id = _toolContext.HostingContext.TeamCityProjectTemplate
        },
        ParentProject = new GetProjectResponse
        {
            Id = parentProjectId
        },
        CopyAllAssociatedSettings = true,
    }));
}

Error:

403 Forbidden: CSRF Header X-TC-CSRF-Token does not match CSRF session value

Also I did a Google search and I tried adding the header origin but I dont have access to disable the internal teamcity properties to disable CSRF check.

So I am passing the token X-tc-CSRF-Token in the request but it says the value doesn't match. How can I solve this issue?

Note: I am getting this issue only when I am using bearer token and with basic auth it works fine.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Ekta
  • 261
  • 1
  • 2
  • 11
  • Any idea on how can i solve this – Ekta May 25 '22 at 06:14
  • Related: [REST API Posting two request simultaneously with different properties fails with 403 status code due to CSRF check](https://stackoverflow.com/q/56780171/1364007) – Wai Ha Lee Oct 11 '22 at 08:53

1 Answers1

0

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).

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92