Jira Rest API with OAuth in .NET

TL;DR > Working example / Show me the code

Link to github

It only took a pandemic to start blogging again. My last blog post is from 5 years ago, at that time I still was a student. Currently I’m a software engineer @ intigriti. This is also the reason why I’m writing this article. At intigriti we need JIRA integration with our customers. We are currently in a POC phase with a very nice customer.

This will be written for JIRA Server (but I assume it’s also usable for JIRA Cloud). There are 3 possible ways to authenticate with JIRA:

  • Username / password
  • Cookie
  • OAuth 1.0a

The first one is not usable @ intigriti since we can’t just ask to the customer to send their username & password ๐Ÿ˜‰

The second one is also not usable since we need M2M communication, so we don’t have access to the JIRA cookies of the user.

So the only viable option was OAuth. I have a lot of experience with OAuth 2 and Open Id Connect but I never touched OAuth 1. Let’s say it’s something from before my time, OAuth 2 is already from 2013 ๐Ÿ™‚

And it doesn’t look like that Atlassian will add OAuth 2.0 any time soon. There is an issue from 2015 that still receives a lot of comments to ‘plz add this’…

Okay let’s get started with the docs: https://developer.atlassian.com/server/jira/platform/oauth/
This all seems to make sense but a .NET example is missing. Also the Atlassian.SDK nuget doesn’t really have a nice explanation on how to use it properly (side note: they even don’t support it)

I found a lot of people struggling to make it work in .NET. Most people just implemented username / password authentication as a result. Imho Atlassian should do something about this since it’s less secure that way. So I’ll write down my findings and a working example ๐Ÿ˜‰

Okay the JIRA side is rather well explained on how to set it up. So I assume you have a public and private key (both in PEM format). And that you configured the AppLink correctly. The only thing you will need is the consumerKey and make sure you uploaded the correct public key.

Step 1: Get consumer secret

var privateKey = File.ReadAllText("jira_privatekey.pem");
// You could also do it without third party nuget
// https://vcsjones.dev/2019/10/07/key-formats-dotnet-3/
var decoder = new OpenSSL.PrivateKeyDecoder.OpenSSLPrivateKeyDecoder();
var keyInfo = decoder.Decode(privateKey);
_consumerSecret = keyInfo.ToXmlString(true);

For this code to work you will need: OpenSSL.PrivateKeyDecoder
You can do it without the nuget checkout: https://vcsjones.dev/2019/10/07/key-formats-dotnet-3/.

This code basically loads the private key and coverts it in a XML file which we need later.

Step 2: Redirect to JIRA

You will need the Atlassian.SDK
This includes a custom created authenticator for RestSharp.

var settings = new OAuthRequestTokenSettings(_url, _consumerKey, _consumerSecret,
                        $"{context.Request.Scheme}://{context.Request.Host}/callback");

A small explanation of the variables:

 _url: The url where your Jira server is running (I'm currently using one through docker-compose)
_consumerKey: The key you used to setup the JIRA AppLink
_consumerSecret: Generated from private key => Step 1

We will also need to add to which url JIRA needs to redirect when the user allowed the integration. In my case this is http://localhost:1234/callback

When we have our settings setup correctly you can now do the following:

var requestToken = await JiraOAuthTokenHelper.GenerateRequestTokenAsync(settings);

This will generate a requestToken that we will need later and a AuthorizeUri. This ‘AuthorizeUri’ is the link were you need the redirect the user to. Since this is a POC and certainly not production ready I’ll save the requestToken in a static dictionary and set the key of the dictionary as cookie value.

 var requestTokenId = Guid.NewGuid();

_requestTokenDb.Add(requestTokenId, requestToken);

context.Response.Cookies.Append("JiraRequestTokenCookie", requestTokenId.ToString());
context.Response.Redirect(requestToken.AuthorizeUri);

Result should be this:

Step 3: Callback

var exists = context.Request.Cookies.TryGetValue("JiraRequestTokenCookie", out var stringRequestTokenId);
if (!exists || !Guid.TryParse(stringRequestTokenId, out var requestTokenId) || !_requestTokenDb.ContainsKey(requestTokenId))
{
    // Incorrect cookie value => Redirect to (/)
    // Code omitted since no real value ๐Ÿ˜‰     
}

var requestToken = _requestTokenDb[requestTokenId];
var verifier = context.Request.Query["oauth_verifier"].ToString();

When JIRA redirects the user back to your application it will add a query param called “oauth_verifier” we will need this param later to request an access token.

And this is where the problem starts. The Atlassian.SDK doesn’t have support to add the “oauth_verifier” param. So I copied the code over from the bitbucket and added a parameter “OAuthVerfier”

public class JiraOAuthAccessTokenSettings
{
    // Copied from https://bitbucket.org/farmas/atlassian.net-sdk/src/master/Atlassian.Jira/OAuth/OAuthAccessTokenSettings.cs

    /// <summary>
    /// Initializes a new instance of the <see cref="Atlassian.Jira.OAuth.OAuthAccessTokenSettings"/> class.
    /// </summary>
    /// <param name="url">The URL of the Jira instance to request to.</param>
    /// <param name="consumerKey">The consumer key provided by the Jira application link.</param>
    /// <param name="consumerSecret">The consumer private key in XML format.</param>
    /// <param name="oAuthRequestToken">The OAuth request token generated by Jira.</param>
    /// <param name="oAuthTokenSecret">The OAuth token secret generated by Jira.</param>
    /// <param name="oAuthVerifier">TODO</param>
    /// <param name="signatureMethod">The signature method used to sign the request.</param>
    /// <param name="accessTokenUrl">The relative URL to request the access token to Jira.</param>
    public JiraOAuthAccessTokenSettings(
        string url,
        string consumerKey,
        string consumerSecret,
        string oAuthRequestToken,
        string oAuthTokenSecret,
        string oAuthVerifier,
        JiraOAuthSignatureMethod signatureMethod = JiraOAuthSignatureMethod.RsaSha1,
        string accessTokenUrl = DefaultAccessTokenUrl)
    {
        Url = url;
        ConsumerKey = consumerKey;
        ConsumerSecret = consumerSecret;
        OAuthRequestToken = oAuthRequestToken;
        OAuthTokenSecret = oAuthTokenSecret;
        OAuthVerifier = oAuthVerifier;
        SignatureMethod = signatureMethod;
        AccessTokenUrl = accessTokenUrl;
    }

And now we can edit the actual implementation to be able to request an access token

/// <summary>
/// Obtain the access token from an authorized request token.
/// </summary>
/// <param name="oAuthAccessTokenSettings">The settings to obtain the access token.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The access token from Jira.
/// Return null if the token was not returned by Jira or the token secret for the request token and the access token don't match.</returns>
public static Task<string> ObtainAccessTokenAsync(JiraOAuthAccessTokenSettings oAuthAccessTokenSettings, CancellationToken cancellationToken)
{
    var authenticator = OAuth1Authenticator.ForAccessToken(
        oAuthAccessTokenSettings.ConsumerKey,
        oAuthAccessTokenSettings.ConsumerSecret,
        oAuthAccessTokenSettings.OAuthRequestToken,
        oAuthAccessTokenSettings.OAuthTokenSecret,
        oAuthAccessTokenSettings.OAuthVerifier);
    authenticator.SignatureMethod = oAuthAccessTokenSettings.SignatureMethod.ToOAuthSignatureMethod();
    
    var restClient = new RestClient(oAuthAccessTokenSettings.Url)
    {
        Authenticator = authenticator
    };
    
    return ObtainAccessTokenAsync(
        restClient,
        oAuthAccessTokenSettings.AccessTokenUrl,
        oAuthAccessTokenSettings.OAuthTokenSecret,
        cancellationToken);
}

It’s also strange to see that RestSharp doesn’t have a constructor that recieves both the OAuthVerifier and the SignatureMethod. I used their integration tests to check out how I needed to implement it and apparently they never needed this scenario in their tests. Since the OAuthVerifier parameter is internal but the SignatureMethod isn’t I still could do what I needed.

var settings = new JiraOAuthAccessTokenSettings(_url, _consumerKey, _consumerSecret, requestToken.OAuthToken, requestToken.OAuthTokenSecret, verifier);
var accessToken = await JiraOAuthTokenHelper.ObtainAccessTokenAsync(settings, CancellationToken.None);

When we finally have the access token:

var jira = Jira.CreateOAuthRestClient(_url, _consumerKey, _consumerSecret, accessToken, requestToken.OAuthTokenSecret);

// JSS is the project key from JIRA 
// The name is for nostalgia reasons
var result = await jira.Issues.GetIssuesFromJqlAsync("project = JSS");
var vm = result.Select(issue => new
{
    Created = issue.Created, Description = issue.Description, Title = issue.Summary,
    Reporter = issue.Reporter, Type = issue.Type.Name, Priority = issue.Priority.Name
});

await context.Response.WriteAsJsonAsync(vm);

This gets all the JIRA issues of the “JSS Project”.

Conclusion

Link to full code: https://github.com/ErazerBrecht/jira_oauth

In the end it isn’t that difficult to do, but I almost spend a whole day searching for it. I hope this will help other devs that have the same problem.

The JAVA and NodeJS example from Atlassian really helped:
JAVA
NodeJS

The NodeJS example is however really outdated, Atlassian also didn’t add the package.json…

The following integrations tests from RestSharp also helped me:
https://github.com/restsharp/RestSharp/blob/dev/test/RestSharp.IntegrationTests/OAuth1Tests.cs

“Big Data” Entity Framework 6

Today I’m going to writeย about handling much data in Entity Framework. On 3/12/2015 I did my first hackathon (Hack The Future @ Antwerp). We had to process a SQL database with 1 000 000 records.

I never had any experience in handling so much data. We are going to use WPF, MVVM and Entity Framework 6 DB First!

First, Hack The Future was an amazing event, I learnt a lot and had some good talks with professionals. For more thank you’s look at the bottom of my post ๐Ÿ™‚

Continue reading

MVVM Entity Framework

“I should get into MVVM”, this is what I said in one of my previous blog post.
Now after a while I decided to port/change a (big) project I’m doing to MVVM (#YOLO).

Now I really want to share some experiences. Like my previous project I used Entity Framework 6.
This time I’ll use SQL rather than SQLite (in code it’s exactly the same). And again I made use of a CollectionViewSource for my DataGrid.

Continue reading

Entity framework Data Validation

So it’s been a while since I posted something on my blog.
The main reason is because I was on holiday. But know I’m back, I just need to write something ๐Ÿ™‚

I’m still experimenting with Entity Framework for a project I’m doing.
And today I want to share a basis concept about data validation.
Again I’ll keep it very straight forwarded. In my search to learn about EF, I find a lot but mostly the examples I find are to difficult or to big for me.

Continue reading

Scoreboard Gitok

My first post about embedded is an old project. In my last year of highschool (2012 – 2013) we had to make a scoreboard for the gym in our school (Gitok). We did this project with the whole class (6TEE). The members involved were Matthias, Steven, Nick, Kristof, Stijn and me! We worked in groups of two people. Every group had another task

Matthias and Steven: (Embedded):ย The actual control of the displays
Nick and Kirstof: (Hardware) The frame, displays, safetyglass, …
Stijn and me: (Software) We had to make the control system.

Continue reading

SQLite EntityFramework 6 Tutorial

Hello,

UPDATE 13/10/2015/
I made another post about MVVM and EntityFramework. After reading this, you should really check that one out!
It’s really worth it to use MVVM ๐Ÿ™‚
Link to another post: Click Here

I’ll explain the basics to get SQLite working with EntityFramework 6. It’s a straight forwarded tutorial / explanation. I will not tell you everything about EF (there are a lot of tuturials on the web). Instead I’ll show you the most basic example to get EF working with SQLite, after all it wasn’t that easy!

Continue reading