Last updatedAug 25, 2017
Improve this page

OAuth for REST APIs

This page shows you how to authenticate clients against the Jira REST API using OAuth (1.0a). We'll explain how OAuth works with Jira, and walk you through an example of how to use OAuth to authenticate a Java application (consumer) against the Jira (resource) REST API for a user (resource owner).

Authentication is a key process when integrating with Jira. OAuth is one of a number of authentication methods used with Jira; and is typically used for server-to-server authentication, especially for behind the firewall applications.

Show me the code!

The code for the final output of this tutorial is here: https://bitbucket.org/atlassian_tutorial/atlassian-oauth-examples (in the 'Java' directory). Feel free to check it out first, if you prefer to figure things out from the source. Otherwise, the guide below will explain what's going on.

Before you begin

Have you picked the right authentication method?

To complete this tutorial, you need the following:

  • A basic knowledge of how to use REST APIs, e.g. requests, responses, headers.
  • A basic understanding of Jira.
  • If you want to run the Java example, you'll need a Jira development instance (get one here), Maven (3.x), and a recent Java version (e.g. Oracle JDK 1.8).

Note

This tutorial was last tested with Jira Cloud (1000.319.1) / Jira Server 7.0.

Overview

OAuth is an authentication protocol that allows a user (resource owner) to grant a third-party application (consumer/client) access to their information on another site (resource). Jira uses 3-legged OAuth (3LO), which means that the user is involved in the authentication process by authorizing access to their data on the resource (as opposed to 2-legged OAuth, where the user is not involved).

Roles

These are the roles in the OAuth authentication process, and how they relate to authenticating with Jira:

Resource
Alt text
If you are integrating a client application with Jira, then Jira is considered to be the "resource".
Resource owner
Alt text
As Jira is the "resource", the Jira user is considered to be the "resource owner" for an authentication request.
Consumer
Alt text
The client application is registered as a consumer, by creating an application link (that uses OAuth) in Jira that links to the client.

Process

The authentication process, commonly known as the "OAuth dance", works by getting the resource owner to grant access to their information on the resource, by authenticating a request token. This request token is used by the consumer to obtain an access token from the resource. Once the client has an access token, it can use it to make authenticated requests to the resource until the token expires or is revoked.

This process is shown in more detail in the diagram below:

Jira OAuth Dance

See it in action

Let's see how the OAuth authentication process actually works, before we look at the code. This will be a hands-on walkthrough, where you'll configure Jira so that the sample client can authenticate against it using OAuth. You'll also be using the sample client to initiate the "OAuth dance", then finally make an authenticated request to the Jira REST API.

In this section:

Before you begin

  • If you haven't gotten the sample OAuth client application code yet, clone it from here: https://bitbucket.org/atlassian_tutorial/atlassian-oauth-examples (in the 'Java' directory).
  • Once you have the code, build the client by running the following command in the root of the project:

    1
    mvn clean compile assembly:single
  • Navigate to the target directory in the project and run:

    1
    java -jar OAuthTutorialClient-1.0.jar requestToken

    Ignore the exception in the output for now. We just need to do this to generate the config.properties file that we'll use later.

Step 1. Configure your client application as an OAuth consumer

In Jira, OAuth consumers are represented by application links. Application links use OAuth with RSA-SHA1 signing for authentication. This means that a private key is used to sign requests, rather than the OAuth token secret/consumer secret. In the following steps, you'll be generating an RSA public/private key pair, then creating a new application link in Jira that uses the key.

Generate an RSA public/private key pair:

  1. In your terminal, run the following openssl commands. You can do this anywhere in your file system.

    1
    2
    3
    4
    openssl genrsa -out jira_privatekey.pem 1024
    openssl req -newkey rsa:1024 -x509 -key jira_privatekey.pem -out jira_publickey.cer -days 365
    openssl pkcs8 -topk8 -nocrypt -in jira_privatekey.pem -out jira_privatekey.pcks8
    openssl x509 -pubkey -noout -in jira_publickey.cer  > jira_publickey.pem

    This generates a 1024 bit private key, creates an X509 certificate, and extracts the private key (PKCS8 format) to the jira_privatekey.pcks8 file. It then extracts the public key from the certificate to the jira_publickey.pem file.

  2. Copy the private key from the jira_privatekey.pcks8 file to your clipboard.

  3. Navigate to the target directory in the sample OAuth client project. Edit the config.properties file and make the following changes:
    • Paste the private key from your clipboard over the value of private_key field. Remove all line breaks.
    • Change the jira_home to the URL of your Jira development instance, e.g. https://example-dev1.atlassian.net
    • The consumer_key should be set to OauthKey.
  4. Save the config.properties file.

Configure the client app as a consumer in Jira, using application links:

  1. In Jira, navigate to cog icon > Applications > Application links.
  2. In the field, 'Enter the URL of the application you want to link', enter any URL, e.g. http://example.com/ and click Create new link. You'll get a warning that 'No response was received from the URL you entered'. Ignore it and click Continue.
  3. On the first screen of the Link applications dialog, enter anything you want in the fields. However, make sure you tick the Create incoming link checkbox.
    Link applications
    Note, in this example, it doesn't matter what you enter for the client application details (URL, name, type, etc). This is because we only want to retrieve data from Jira, therefore we only need to set up a one-way (incoming) link from the client to Jira.
  4. The next screen of the Link applications dialog is where you enter the consumer details for the sample client. Set it to the following values:
    • Consumer key = OauthKey
    • Consumer name = Example Jira app
    • Public key = <Copy the public key from the jira_publickey.pem file you generated previously and paste it into this field> Copying public keys
  5. Click Continue. You should end up with an application link that looks like like this:
    Approve the flow

That's it! You've now configured the sample client as an OAuth consumer in Jira.

Step 2. Do the OAuth dance

The "OAuth dance" is a term that's used to describe the process of getting an access token from the resource, that the consumer can use to access information on the resource. This involves a "dance" where different tokens are passed between the consumer, resource owner, and resource (see OAuth overview above).

  1. In your terminal, navigate to the target directory of the sample OAuth client project, if you are not already there.
  2. Run the following command. This command requests an unauthorized request token from your Jira instance.

    1
    java -jar OAuthTutorialClient-1.0.jar requestToken

    You'll see the following output with the details of your new request token:

    1
    2
    3
    Token:            ec3dj4byySM5ek3XW7gl7f4oc99obAlo
    Token Secret:   OhONj0eF7zhXAMKZLbD2Rd3x7Dmxjy0d
    Retrieved request token. go to https://jira101.atlassian.net/plugins/servlet/oauth/authorize?oauth_token=ec3dj4byySM5ek3XW7gl7f4oc99obAlo to authorize it

    The request token will persist for 10 minutes. If it expires, you'll need to request a new one.

  3. In your browser, go to the URL specified in the output. You'll see the following dialog: Approve the flow

  4. Click Allow. This will authorize the request token. You'll see the following output in your browser:

    1
    2
    Access Approved
    You have successfully authorized 'Example Jira app'. Your verification code is 'qTJkPi'. You will need to enter this exact text when prompted. You should write this value down before closing the browser window.

    Copy the verification code to your clipboard or somewhere else where you can get it.

  5. In your terminal, run the following command. You'll need to replace the example verification code ("qTJkPi") with your own verification code from the previous step.

    1
    java -jar OAuthTutorialClient-1.0.jar accessToken qTJkPi

    You'll see the following output in your terminal:

    1
    Access Token:         W1jjOV4sq2iEqxO4ZYZTJVdgbjtKc2ye

    Note, in OAuth, you actually need to pass the consumer key, request token, verification code, and private key, to get the access token. However, in the sample client, information like the consumer key, request token, private key, etc, are stored in the config.properties file when they are generated (have a look at it, as you complete this tutorial and you'll see the new values added). You probably don't want to do this for a production implementation, but this makes the sample client easier to use for this example.

We now have what we wanted from the OAuth dance: an access token that we can use to make an authenticated request to the Jira REST API.

Step 3. Make an authenticated request to the Jira REST API

An access code is all that we need to make an authenticated request to the Jira REST API. Requests are made as the user who authorized the initial request token. The access token will persist for 5 years, unless it is revoked.

The sample OAuth client only makes GET requests, but it's simple to use. Just run the following command. You'll need to substitute <URL for REST method> with the URL of the REST method that you are trying to call.

1
java -jar OAuthTutorialClient-1.0.jar request <URL for GET method>

In the code, the sample OAuth client actually stores the access code in the config.properties file when it is obtained. When a request is made, the sample client passes the stored access code, rather than you having to enter it.

Here's an example of a request to get an issue, using the sample OAuth client -- the following command gets the issue, JJ-2, via the Jira REST API:

1
java -jar OAuthTutorialClient-1.0.jar request https://example-dev1.atlassian.net/rest/api/latest/issue/JJ-2

The above method returns an issue like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
{
  "expand": "renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations",
  "self": "<a href="https://example-dev1.atlassian.net/rest/api/latest/issue/10300">https://example-dev1.atlassian.net/rest/api/latest/issue/10300</a>",
  "id": "10300",
  "fields": {
    "issuetype": {
      "avatarId": 10803,
      "name": "Bug",
      "self": "<a href="https://example-dev1.atlassian.net/rest/api/2/issuetype/1">https://example-dev1.atlassian.net/rest/api/2/issuetype/1</a>",
      "description": "A problem which impairs or prevents the functions of the product.",
      "id": "1",
      "iconUrl": "<a href="https://example-dev1.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10803&avatarType=issuetype">https://example-dev1.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10803&avatarType=issuetype</a>",
      "subtask": false
    },
    "timespent": null,
    "project": {
      "avatarUrls": {
        "48x48": "<a href="https://example-dev1.atlassian.net/secure/projectavatar?pid=10200&avatarId=10700">https://example-dev1.atlassian.net/secure/projectavatar?pid=10200&avatarId=10700</a>",
        "24x24": "<a href="https://example-dev1.atlassian.net/secure/projectavatar?size=small&pid=10200&avatarId=10700">https://example-dev1.atlassian.net/secure/projectavatar?size=small&pid=10200&avatarId=10700</a>",
        "16x16": "<a href="https://example-dev1.atlassian.net/secure/projectavatar?size=xsmall&pid=10200&avatarId=10700">https://example-dev1.atlassian.net/secure/projectavatar?size=xsmall&pid=10200&avatarId=10700</a>",
        "32x32": "<a href="https://example-dev1.atlassian.net/secure/projectavatar?size=medium&pid=10200&avatarId=10700">https://example-dev1.atlassian.net/secure/projectavatar?size=medium&pid=10200&avatarId=10700</a>"
      },
      "name": "Jira Junior",
      "self": "<a href="https://example-dev1.atlassian.net/rest/api/2/project/10200">https://example-dev1.atlassian.net/rest/api/2/project/10200</a>",
      "id": "10200",
      "key": "JJ"
    },
    "fixVersions": [],
    "aggregatetimespent": null,
    "resolution": null,
    "customfield_10500": null,
    "customfield_10700": "com.atlassian.servicedesk.plugins.approvals.internal.customfield.ApprovalsCFValue@c8d588",
    "resolutiondate": null,
    "workratio": -1,
    "lastViewed": "2016-08-01T11:23:39.481+1000",
    "watches": {
      "self": "<a href="https://example-dev1.atlassian.net/rest/api/2/issue/JJ-2/watchers">https://example-dev1.atlassian.net/rest/api/2/issue/JJ-2/watchers</a>",
      "isWatching": true,
      "watchCount": 1
    },
    "created": "2013-05-29T13:56:24.224+1000",
    "customfield_10020": null,
    "customfield_10021": "Not started",
    "priority": {
      "name": "Major",
      "self": "<a href="https://example-dev1.atlassian.net/rest/api/2/priority/3">https://example-dev1.atlassian.net/rest/api/2/priority/3</a>",
      "iconUrl": "<a href="https://example-dev1.atlassian.net/images/icons/priorities/major.svg">https://example-dev1.atlassian.net/images/icons/priorities/major.svg</a>",
      "id": "3"
    },
    "customfield_10300": null,
    "customfield_10102": null,
    "labels": [],
    "customfield_10016": null,
    "customfield_10017": null,
    "customfield_10018": null,
    "customfield_10019": null,
    "timeestimate": null,
    "aggregatetimeoriginalestimate": null,
    "versions": [],
    "issuelinks": [],
    "assignee": {
      "emailAddress": "alana@<a href="http://example.com">example.com</a>",
      "avatarUrls": {
        "48x48": "<a href="https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=48">https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=48</a>",
        "24x24": "<a href="https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=24">https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=24</a>",
        "16x16": "<a href="https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=16">https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=16</a>",
        "32x32": "<a href="https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=32">https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=32</a>"
      },
      "displayName": "Alana Example",
      "name": "alana",
      "self": "<a href="https://example-dev1.atlassian.net/rest/api/2/user?username=alana">https://example-dev1.atlassian.net/rest/api/2/user?username=alana</a>",
      "active": true,
      "timeZone": "Australia/Sydney",
      "key": "alana"
    },
    "updated": "2016-08-01T11:23:38.022+1000",
    "status": {
      "name": "Open",
      "self": "<a href="https://example-dev1.atlassian.net/rest/api/2/status/1">https://example-dev1.atlassian.net/rest/api/2/status/1</a>",
      "description": "The issue is open and ready for the assignee to start work on it.",
      "iconUrl": "<a href="https://example-dev1.atlassian.net/images/icons/statuses/open.png">https://example-dev1.atlassian.net/images/icons/statuses/open.png</a>",
      "id": "1",
      "statusCategory": {
        "colorName": "blue-gray",
        "name": "To Do",
        "self": "<a href="https://example-dev1.atlassian.net/rest/api/2/statuscategory/2">https://example-dev1.atlassian.net/rest/api/2/statuscategory/2</a>",
        "id": 2,
        "key": "new"
      }
    },
    "components": [],
    "timeoriginalestimate": null,
    "description": "The logo is currently a light cerise. I'd like to see it with a deep pink color.",
    "customfield_10012": null,
    "customfield_10013": null,
    "customfield_10014": null,
    "timetracking": {},
    "customfield_10015": null,
    "customfield_10600": null,
    "customfield_10006": "10",
    "customfield_10601": null,
    "customfield_10007": [
      "com.atlassian.greenhopper.service.sprint.Sprint@dc6300[id=1,rapidViewId=<null>,state=CLOSED,name=Sprint 1,goal=<null>,startDate=2013-07-26T11:31:09.530+10:00,endDate=2013-08-09T11:31:09.530+10:00,completeDate=2013-07-26T11:31:46.489+10:00,sequence=1]",
      "com.atlassian.greenhopper.service.sprint.Sprint@6b3e17[id=2,rapidViewId=<null>,state=ACTIVE,name=Sprint 2,goal=<null>,startDate=2013-08-22T11:35:33.759+10:00,endDate=2013-12-12T11:35:00.000+11:00,completeDate=<null>,sequence=2]"
    ],
    "customfield_10008": null,
    "attachment": [],
    "aggregatetimeestimate": null,
    "summary": "Jira Junior logo is not pink enough",
    "creator": {
      "emailAddress": "admin@<a href="http://example.com">example.com</a>",
      "avatarUrls": {
        "48x48": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48</a>",
        "24x24": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24</a>",
        "16x16": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16</a>",
        "32x32": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32</a>"
      },
      "displayName": "Administrator",
      "name": "admin",
      "self": "<a href="https://example-dev1.atlassian.net/rest/api/2/user?username=admin">https://example-dev1.atlassian.net/rest/api/2/user?username=admin</a>",
      "active": true,
      "timeZone": "Australia/Sydney",
      "key": "admin"
    },
    "subtasks": [],
    "reporter": {
      "emailAddress": "admin@<a href="http://example.com">example.com</a>",
      "avatarUrls": {
        "48x48": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48</a>",
        "24x24": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24</a>",
        "16x16": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16</a>",
        "32x32": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32</a>"
      },
      "displayName": "Administrator",
      "name": "admin",
      "self": "<a href="https://example-dev1.atlassian.net/rest/api/2/user?username=admin">https://example-dev1.atlassian.net/rest/api/2/user?username=admin</a>",
      "active": true,
      "timeZone": "Australia/Sydney",
      "key": "admin"
    },
    "customfield_10000": null,
    "aggregateprogress": {
      "total": 0,
      "progress": 0
    },
    "customfield_10001": null,
    "customfield_10200": "0|10001s:",
    "customfield_10002": null,
    "customfield_10003": null,
    "customfield_10400": null,
    "environment": null,
    "duedate": null,
    "progress": {
      "total": 0,
      "progress": 0
    },
    "comment": {
      "total": 1,
      "comments": [{
        "author": {
          "emailAddress": "admin@<a href="http://example.com">example.com</a>",
          "avatarUrls": {
            "48x48": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48</a>",
            "24x24": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24</a>",
            "16x16": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16</a>",
            "32x32": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32</a>"
          },
          "displayName": "Administrator",
          "name": "admin",
          "self": "<a href="https://example-dev1.atlassian.net/rest/api/2/user?username=admin">https://example-dev1.atlassian.net/rest/api/2/user?username=admin</a>",
          "active": true,
          "timeZone": "Australia/Sydney",
          "key": "admin"
        },
        "created": "2013-06-04T16:11:24.505+1000",
        "updateAuthor": {
          "emailAddress": "admin@<a href="http://example.com">example.com</a>",
          "avatarUrls": {
            "48x48": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48</a>",
            "24x24": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24</a>",
            "16x16": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16</a>",
            "32x32": "<a href="https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32</a>"
          },
          "displayName": "Administrator",
          "name": "admin",
          "self": "<a href="https://example-dev1.atlassian.net/rest/api/2/user?username=admin">https://example-dev1.atlassian.net/rest/api/2/user?username=admin</a>",
          "active": true,
          "timeZone": "Australia/Sydney",
          "key": "admin"
        },
        "self": "<a href="https://example-dev1.atlassian.net/rest/api/2/issue/10300/comment/10100">https://example-dev1.atlassian.net/rest/api/2/issue/10300/comment/10100</a>",
        "id": "10100",
        "body": "Hi [~william] Jira is *super fun* (see JJ-1). Alana is going to fix the logo.",
        "updated": "2013-06-12T21:55:34.882+1000"
      }],
      "maxResults": 1,
      "startAt": 0
    },
    "votes": {
      "hasVoted": false,
      "self": "<a href="https://example-dev1.atlassian.net/rest/api/2/issue/JJ-2/votes">https://example-dev1.atlassian.net/rest/api/2/issue/JJ-2/votes</a>",
      "votes": 0
    }
  },
  "key": "JJ-2"
}

Tick Congratulations! You now know how to use OAuth to make an authenticated request to the Jira REST API.

Development tips

You can implement OAuth in a number of ways, depending on what you are building. However, we encourage you to browse the source code for our sample Java OAuth client to get an idea of how it is implemented, regardless of the technologies that you are using.

In addition, you'll find a number of general tips below that should be helpful, regardless of how you are implementing OAuth.