JIRA Cloud REST call made with addon user

When I’m making a REST call from my NodeJS Server it’s always making it with the addon user and not with the user logged. Also, I’m trying to make REST calls with the httpClient.asUser('admin') to force if the user of the REST call is correct.

The request context that I’m sending to addon.httpClient it’s like that:

 { http:
   HostClient {
     addon:
      EventEmitter {
        app: [Object],
        logger: [Object],
        config: [Object],
        settings: [Object],
        schema: [Object],
        descriptor: [Object],
        key: 'XXXXX',
        name: 'XXXXX',
        _: [Object],
        RSVP: [Object],
        _jwt: [Object],
        shouldRegister: [Function],
        shouldDeregister: [Function],
        register: [Function],
        deregister: [Function],
        watcher: [Object],
        _registrations: {} },
     context: 'admin',
     clientKey: 'XXX',
     oauth2: OAuth2 { addon: [Object] } },
  title: 'XXXXX',
  addonKey: 'XXXXX',
  userId: 'admin',
  clientKey: 'XXX',
  token: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
  license: 'none',
  timeZone: 'Europe/Madrid',
  locale: 'en-US',
  localBaseUrl: 'https://XXXXX.ngrok.io',
  hostBaseUrl: 'https://davidcloud.atlassian.net',
  hostUrl: 'https:',
  hostStylesheetUrl: 'https://davidcloud.atlassian.net/atlassian-connect/all-debug.css',
  hostScriptUrl: 'https://davidcloud.atlassian.net/atlassian-connect/all-debug.js' }

And the call:

    var httpClient = addon.httpClient(req);
    httpClient.asUser('admin').get('/rest/api/2/myself', function (err, res, body) {
            console.log('err',err);    //TODO remove
            console.log('body',body);    //TODO remove
    })

The response of the last example always is

    { error: 'invalid_grant',
      error_description: 'The token is expired' }

Anyone has an example of REST authenticate calls from NodeJS in backend?

Any help is very appreciated. Thanks.

1 Like

Jira cloud add-ons can act on behalf of users - https://developer.atlassian.com/blog/2016/10/Atlassian-Connect-now-allows-Add-ons-to-make-requests-on-behalf-of-a-user/

I’m running the following code in the enabled method of addon:

      var jwtClaims = {};
		addon.settings.get('clientInfo', req.body.clientKey).then(function (data) {
            console.log('VAQ','data',data);    //TODO remove

            var now = Math.floor(Date.now() / 1000),
                exp = now + 60;

            jwtClaims.iss = "urn:atlassian:connect:clientid:" + data.oauthClientId;
            jwtClaims.sub = "urn:atlassian:connect:userkey:" + 'admin';
			jwtClaims.tnt = data.baseUrl;
			jwtClaims.aud = "https://auth.atlassian.io";
			jwtClaims.iat = now;
			jwtClaims.exp = exp;

            var assertion = jwt.encode(jwtClaims, data.sharedSecret);

            var parameters = {
                grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
                assertion: assertion,
                scope: SCOPES
            };

            console.log("\nRequesting access token".bold);

            request.post({
                url: AUTHORIZATION_SERVER_URL + '/oauth2/token',
                form: parameters,
                json: true,
                headers: {
                    "accept": "application/json"
                }
            }, function(err, httpResponse, body) {
                var statusCode = httpResponse.statusCode;
                if (err || statusCode < 200 || statusCode > 299) {
                    var error = err || body;
                    console.log(("ERROR [" + statusCode + "]").red + ": Couldn't get access token from response\n", error);
                } else {
                    console.log("Token type:".yellow, body.token_type);
                    console.log("Access token:".yellow, body.access_token);
                    makeRequestAsUser(body.access_token);
                }
            });
        });

But I always receive the same response in the post REST call:

    { error: 'invalid_grant',
      error_description: 'The token is expired' }

I do something similar and it works:

  app.post('/enabled', addon.authenticate(), function(req, res) {
    addon
      .httpClient(req)
      .asUser(req.query.user_key) // act as user who is installing/enabling add-on
      .get('/rest/api/2/myself', function(err, res, body) {
          console.log(err, res.statusCode, body);
      });
    res.send();
  });

It prints:

null 200 '{"self":"https://timereports.atlassian.net/rest/api/2/user?username=admin","key":"admin", ...

Using atlassian-connect-express 2.0.3

Ok, so as I understand, you cant get OAuth2 token?

What value do you have for this constant? It should be https://auth.atlassian.io - not JIRA instance url

I’ve created a new addon only to test this, using the ‘atlas-connect new’ command. I’ve only added your code in the enable lifecycle, but it returns me the same response ‘The token is expired’.

I’m using the same atlassian-connect-express version, 2.0.3.

What can I do? This is blocking us in our development.

Great thanks. Cheers.

I can’t get the OAuth2 token.

This variable has https://auth.atlassian.io.

Great thanks. Cheers

Do you have any new idea to test? Do you know why the REST responds always with the same result?

We’re a little stuck here.

Cheers

Can you show us the raw content of the HTTP request that you are sending to the OAuth 2.0 authorization server? There’s a couple of things I’d like to check:

a) Does the Content-Type header get set to application/x-www-form-urlencoded?

b) Is the grant_type url-encoded?
i.e. urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer

I add the content-type to the header because I don’t set this, with respect the grant_type I use urn:ietf:params:oauth:grant-type:jwt-bearer

Now my request to OAuth 2.0 is this:

var  SCOPES = "READ ADMIN ACT_AS_USER";
var AUTHORIZATION_SERVER_URL = "https://auth.atlassian.io";

var jwtClaims = {};
jwtClaims.iss = "urn:atlassian:connect:clientid:" + data.oauthClientId;
jwtClaims.sub = "urn:atlassian:connect:userkey:" + 'admin';
jwtClaims.tnt = data.baseUrl;
jwtClaims.aud = "https://auth.atlassian.io";
jwtClaims.iat = now;
jwtClaims.exp = exp;

var assertion = jwt.encode(jwtClaims, data.sharedSecret);

var parameters = {
    grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
    assertion: assertion,
    scope: SCOPES
};

request.post({
    url: AUTHORIZATION_SERVER_URL + '/oauth2/token',
    form: parameters,
    json: true,
    headers: {
        "accept": "application/json",
        "Content-Type": "application/x-www-form-urlencoded"
    }
}, function(err, httpResponse, body) {
    var statusCode = httpResponse.statusCode;
    if (err || statusCode < 200 || statusCode > 299) {
        var error = err || body;
        console.log(("ERROR [" + statusCode + "]").red + ": Couldn't get access token from response\n", error);
    } else {
        console.log("Token type:", body.token_type);
        console.log("Access token:", body.access_token);
    }
});

But the result is the same:

ERROR [400]: Couldn't get access token from response
 { error: 'invalid_grant',
  error_description: 'The token is expired' }

I’m using the atlassian-jwt library and 2.0.3 of atlassian-connect-express

It’s not spelled out particularly well in the documentation, but I understand that you need to URL-encode the grant_type value. The example request on the relevant doco page is:

POST /oauth2/token HTTP/1.1
Host: auth.atlassian.io
Accept: application/json
Content-Length: {length of the request body}
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&scope=READ+WRITE&assertion={your-signed-jwt}

That’s what I mean by ‘the raw content of the HTTP request’. You can see that the grant_type has all those weird escape sequences in it.

When execute the REST the raw header is that:

POST /oauth2/token HTTP/1.1
HOST: auth.atlassian.io
accept: application/json
content-length: 609
content-type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&scope=READ+ADMIN+ACT_AS_USER&assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ1cm46YXRsYXNzaWFuOmNvbm5lY3Q6Y2xpZW50aWQ6ZXlKb2IzTjBTMlY1SWpvaWFtbHlZVG94TlRZeVpqZGhZeTFsTTJWaUxUUmtPREF0WVRRd1pDMHhObUprWVRjeE1EVmxPRFFpTENKaFpHUnZia3RsZVNJNkltTnZiUzVrWldselpYSXVhbWx5WVM1bGVIQnZjblJsY2lKOSIsInN1YiI6InVybjphdGxhc3NpYW46Y29ubmVjdDp1c2Vya2V5OmFkbWluIiwidG50IjoiaHR0cHM6Ly9kYXZpZGNsb3VkLmF0bGFzc2lhbi5uZXQiLCJhdWQiOiJodHRwczovL2F1dGguYXRsYXNzaWFuLmlvIiwiaWF0IjoxNDk1MTE0MzQxLCJleHAiOjE0OTUxMTQ0NjF9.BZqJR_hEkzGutz2klerTDYSnvk0vrjWNmHJen9_1FJ4

As you can see I use the content-type and grant-type encoded.

That all looks fine. The only thing I can think of is that the shared secret might be bad. Could you try re-installing the add-on so that the shared secret is re-obtained from the host? (clutching at straws here)

Hi,

Sorry for don’t respond you but I thought that I made it.

I tried to reinstalling the add-on in two different instances many times but the result is the same in all cases.

Cheers

Recently I began to receive same errors when I test application on localhost via ngrok. But all works fine when same application is running on test server.

Upd: The problem was in computer clock’s late. The problem has gone after time synchronization.

1 Like

Hi @artema

Sorry for the delay. I saw the same solution for this problem in another post.

Thank you so much!