Triggers in iHub Cloud

Triggers in iHub Cloud

Json Web Token - JWT

To access a integration in iHub using a JWT the user must setup a JWT Trigger.

This is done by toggle the JWT option on under webhook triggers.

JSON Web Tokens consist of three parts separated by dots (.), which are:

  • Header

  • Payload

  • eSignature

Therefore, a iHub JWT looks like the following.

xxxxx.yyyyy.zzzzz

The JWT token is then exchanged to a bearer which will be the short lived token that authenticate and triggers the integration flow.

 

image-20240529-070325.png

 

RSA 256 Key Pair

First generate a RSA Key pair and download the the key, once downloaded the key can not be recovered if lost. Only option if the key is lost is to generate a new key.

The downloaded file will contain the information needed to exchange the JWT to a Bearer token.

Sign the JWT token

To sign the JWT token use the header and payload.

Header

{ "alg": "RS256", "typ": "JWT" }

Payload

{ "iss": "client_id_from_atlassian", "sub": "flow_id", "aud": "https://ihubprod.rixter.net/prod/incoming/token", "iat": "currentTimeSeconds", "exp": "plus currentTimeSeconds 1200", }

Output from the signed will be xxxxx.yyyyy.zzzzz

Exchange the JWT to Bearer

To exchange the JWT to a short lived bearer token,

POST to https://ihubprod.rixter.net/incoming/token

Body:

{ "grant_type":"urn:ihub:jwt:bearer", "assertion":"{{JWT}}" }

Replace the {{JWT}} with the signed token from above step.

This URN structure indicates:

  • urn: Namespace denoting a Uniform Resource Name

  • ihub: Identifies the IHub system

  • jwt: Specifies a JSON Web Token (JWT)

  • bearer: Indicates the token type is bearer

Response

If valid it will return a body like below

{ access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI0ZjAxOWQ0My00OTAwLTNhOTgtYWEwNi00NjE0M2Y0MGMwNjEiLCJzdWIiOiIzNGNkMzNlNC1iNmFhLTQ4MzctODc1Yi03ZWQ4MWFhMzk2ZDYiLCJleHAiOjE3MTkzMTk0MDMsImlhdCI6MTcxOTMxOTA4MX0.jljJZJMcYD4PapgraNXoZZWUYOR3mPcTgpd_CUeeqCU', token_type: 'Bearer', expires_in: 1719319403 }

Use the Bearer token

To trigger the flow send the request to https://ihubprod.rixter.net/incoming/webhook/jwt with the http header Authorization: Bearer {{access_token}}

In this call you will include any data that the integration will process.

 

 

Example code for trigging flow

Python

pip install PyJWT requests cryptography

The private key is stored in a file called privateKey.txt in this example

import jwt import requests import time import os from pathlib import Path import unittest class TestIncomingJWTIntegration(unittest.TestCase): def setUp(self): self.timeout = 10 # seconds self.maxDiff = None def test_should_return_a_valid_bearer(self): headers = { "alg": "RS256", "typ": "JWT" } current_time = int(time.time()) payload = { 'iss': 'copy from trigger page', 'sub': 'copy from trigger page', 'aud': 'https://ihubprod.rixter.net/prod/incoming/token', 'iat': current_time, 'exp': current_time + 1200 } private_key_path = Path(__file__).parent / 'privateKey.txt' with open(private_key_path, 'r') as file: private_key = file.read() token = jwt.encode(payload, private_key, algorithm='RS256', headers=headers) # print('Generated JWT:', token) data = { "grant_type": "urn:ihub:jwt:bearer", "assertion": token } access_token = None # test generate jwt try: response = requests.post(payload['aud'], json=data, headers={'Content-Type': 'application/json'}) response.raise_for_status() access_token = response.json().get('access_token') except requests.exceptions.RequestException as error: print('Error:', error.response.json() if error.response else str(error)) self.assertIsNotNone(access_token) # test trigger flow incoming_data = { "issueKey": "SERVICENOW-1", "description": "HEJ", "summary": "Ticket from test case" } try: response = requests.post( "https://ihubprod.rixter.net/prod/incoming/webhook/jwt", json=incoming_data, headers={ 'Content-Type': 'application/json', 'Authorization': f'Bearer {access_token}' } ) response.raise_for_status() print(response.json()) except requests.exceptions.RequestException as error: print('Error:', error.response.json() if error.response else str(error)) if __name__ == '__main__': unittest.main()

Javascript

The private key is stored in a file called privateKey.txt in this example

var jwt = require('jsonwebtoken'); const axios = require('axios'); import { expect } from "chai"; const fs = require('fs'); const path = require('path'); describe('Incoming JWT integration test', function() { // Use function() to access this.timeout this.timeout(10000); // Increase timeout to 10 seconds it('should return a valid bearer', async () => { const headers = { "alg": "RS256", "typ": "JWT" }; const currentTime = Math.floor(Date.now() / 1000); const payload = { iss: 'copy from trigger page', sub: 'copy from trigger page', aud: 'https://ihubprod.rixter.net/prod/incoming/token', iat: currentTime, exp: (currentTime + 1200) }; const privateKeyPath = path.join(__dirname, 'privateKey.txt'); const privateKey = fs.readFileSync(privateKeyPath, 'utf8'); const token = jwt.sign(payload, privateKey, { algorithm: 'RS256', header: headers }); //console.log('Generated JWT:', token); const data = { "grant_type": "urn:ihub:jwt:bearer", "assertion": token }; let access_token; //test generate jwt try { const response = await axios.post(payload.aud, data, { headers: { 'Content-Type': 'application/json' } }); access_token = response.data.access_token; } catch (error) { console.error('Error:', error.response ? error.response.data : error.message); } expect(access_token).not.to.be.equal(undefined); //test trigger flow const incomingData = { "issueKey": "SERVICENOW-1", "description": "HEJ", "summary":"Ticket from test case" }; try { const response = await axios.post("https://ihubprod.rixter.net/prod/incoming/webhook/jwt", incomingData, { headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer '+access_token } }); console.log(response.data) } catch (error) { console.error('Error:', error.response ? error.response.data : error.message); } }); });