Skip to main content

JIRA REST API: Cookie-based Authentication

Three authentication methods are proposed by the JIRA REST API documentation:
  • Basic Authentication is a simple but not very safe approach. Credentials are sent in the header on every request and encoding to Base64 is not a proper protection in this case; HTTPS connection is required.
  • OAuth authentication - looks a bit complex and requires additional configuration at the JIRA server that is not always possible.
  • Cookie-based Authentication - this approach seems to be the most convinient one: credentials are checked once, then the authentication cookie only is sent on subsequent requests.

However, trying to use the cookie-based authentication I encountered an issue. The approach described in the documentation worked partially: I was able to create a new session and get the response containing the session cookie but all subsequent requests using this session cookie were rejected as unauthorized. Spending some time investigating this I found the cause of the issue: JSESSIONID is not the only cookie which is required.

I am testing on a demo account which JIRA provides for trials on Atlassian Cloud. In my case for the proper authentication are also required:

  • atlassian.xsrf.token - Atlassian Cloud service's Server ID, a securely-generated random string (i.e. token) and a flag that indicates whether or not the user was logged in at the time the token was generated.
  • studio.crowd.tokenkey - authentication cookie which is used for single sign-on (SSO) between Atlassian Cloud applications. Like the JSESSIONID cookie, this cookie also contains a random string and the cookie expires at the end of every session or when the browser is closed.
More details on JIRA API cookies used by Atlassian Cloud are here: https://confluence.atlassian.com/cloud/cookies-744721661.html. I suppose in case of internal hosting of the JIRA server the cookies set may differ so a safe approach seems to be to re-send all cookies the method got during creation of the session.

Below is the example of usage of the cookie-based authentication to retrieve data from JIRA API.

    var baseUrl = //URL to your JIRA server, e.g.  https://<YOUR ACCOUNT>.atlassian.net
    var userName = ...
    var password = ...

    var dataProvider = new JiraApi();
    IDictionary<string, string> cookies;
    if (dataProvider.TryLogin(baseUrl, userName, password, out cookies))
    {
         //just for example: retrieves all issues of the specified type
         dataProvider.SearchForIssues(baseUrl, cookies, "type=Story"); 
         dataProvider.Logout(baseUrl, cookies);
    }

Actually the logout is not a required step; the cookies can be saved somewhere and used until they expired (30 days).

I used RestSharp to keep the below code simple, however the approach should also work with the plain HttpWebRequest. Also for simplicity of the example I used client.Execute instead of more convinient client.Execute<T> which allows to deserialize the response content from JSON to entities.

    
    public class JiraApi
    {
        public bool TryLogin(string baseUrl, string userName, string password, 
              out IDictionary<string, string> cookies)
        {
            var client = new RestClient(baseUrl);

            var request = new RestRequest("/rest/auth/1/session", Method.POST);
            request.RequestFormat = DataFormat.Json;
            request.AddBody(new { username = userName, password = password });

            var response = client.Execute(request);
            if (response.StatusCode == HttpStatusCode.OK)
            {
                cookies = response.Cookies.ToDictionary(x => x.Name, x => x.Value);
                return true;
            }
            else
            {
                //handle the cause of the failure based on information provided 
                //by response.StatusCode and response.Content
                //...

                cookies = null;
                return false;
            }
        }

        public void SearchForIssues(string baseUrl, IDictionary<string, string> cookies, string jql)
        {
            var client = new RestClient(baseUrl);
            var request = new RestRequest("/rest/api/2/search", Method.GET);
            request.RequestFormat = DataFormat.Json;
            request.AddBody(new { jql = jql });

            foreach (var cookie in cookies)
                request.AddCookie(cookie.Key, cookie.Value);

            var response = client.Execute(request);
            if (response.StatusCode == HttpStatusCode.OK)
            {
                var content = response.Content;

                //handle results here
                //...

            }
            else
            {
                //handle the cause of the failure based on information provided 
                //by response.StatusCode and response.Content
                //...
            }
        }

        public void Logout(string baseUrl, IDictionary<string, string> cookies)
        {
            var client = new RestClient(baseUrl);
            var request = new RestRequest("/rest/auth/1/session", Method.DELETE);
            foreach (var cookie in cookies)
                request.AddCookie(cookie.Key, cookie.Value);

            var response = client.Execute(request);
            if (response.StatusCode == HttpStatusCode.NoContent)
            {
                //handle results here
                //...

            }
            else
            {
                //handle the cause of the failure based on information provided 
                //by response.StatusCode and response.Content
                //...
            }
        }

Note that the successful deletion of the session returns NoContent status code instead of OK.

Comments

  1. Do I need the paid version to work with the API. Is it possible to do it for free?

    ReplyDelete
    Replies
    1. I tested on a free trial Atlassian Cloud account.

      Delete

Post a Comment

Popular posts from this blog

How to merge cells with equal values in the GridView

My solution is not the first; however, I think, it is rather universal and very short - less than 20 lines of the code.

The algorithm is simple: to bypass all the rows, starting from the second at the bottom, to the top. If a cell value is the same as a value in the previous (lower) row, then increase RowSpan and make the lower cell invisible, and so forth.

The code that merges the cells is very short:
public class GridDecorator { public static void MergeRows(GridView gridView) { for (int rowIndex = gridView.Rows.Count - 2; rowIndex >= 0; rowIndex--) { GridViewRow row = gridView.Rows[rowIndex]; GridViewRow previousRow = gridView.Rows[rowIndex + 1]; for (int i = 0; i < row.Cells.Count; i++) { if (row.Cells[i].Text == previousRow.Cells[i].Text) { row.Cells[i].RowSpan = previousRow.Cells[i].RowSpan < 2 ? 2 : prev…

Merging columns in GridView/DataGrid header

As necessity to show header columns in a few rows occurs fairly often it would be good to have such functionality in the GridView/DataGrid control as an in-built feature. But meanwhile everyone solves this problem in his own way.

The described below variant of the merging implementation is based on irwansyah's idea to use the SetRenderMethodDelegate method for custom rendering of grid columns header. I guess this approach can be simplified in order to get more compact and handy code for reuse.
The code overview
As it may be required to merge a few groups of columns - for example, 1,2 and 4,5,6 - we need a class to store common information about all united columns.
[Serializable]
private class MergedColumnsInfo
{
// indexes of merged columns
public List<int> MergedColumns = new List<int>();
// key-value pairs: key = the first column index, value = number of the merged columns
public Hashtable StartColumns = new Hashtable();
// key-value pairs: key = the first column in…