API Access App target URL Verification

Hi all,

I have a API Access application running on an express server. The application uses the SmartThings NodeJS SDK to setup subscriptions.

The target url for SmartThings to ping my express server with data for these subscription was the index route of my application, i.e / route in my app. The portion of code to implement this is shown below using the handleHttpCallback function.

var express         = require('express');
var router          = express.Router();
var smartapp        = require('../smartapp');

/// handleIndexPost ///
// This is the route that smarthings will post to 
const handleSmartThingsPost = async function (req, res, next) {
    try{
        // Handle the http callback 
        let result = await smartapp.handleHttpCallback(req, res);
    }catch(error){
        logger.error(`Error in handling post request to \ ${error}`)
    }
}

/// POST home page. ///
// req and res are passed automatically to handleSmartThingsPost 
router.post('/', smartThingsController.handleSmartThingsPost)

On the first request to verify this is the correct url the message below is printed to the console. I simply click on the <URL> in the message and the route is verified and all works well.

2021-03-24T16:13:58.814Z info: CONFIRMATION request for app XXXX, to enable events visit <URL>

I want to now instead of receiving data from SmartThings at the route / I want to receive data at the route /smartthings.
The code is pretty much the same as shown below to enable this.

var express         = require('express');
var router          = express.Router();
var smartapp        = require('../smartapp')

/// handleIndexPost ///
// This is the route that smarthings will post to 
const handleSmartThingsPost = async function (req, res, next) {
    try{
        // Handle the http callback 
        let result = await smartapp.handleHttpCallback(req, res);
    }catch(error){
        logger.error(`Error in handling post request to \ ${error}`)
    }
}

/// POST home page. ///
// req and res are passed automatically to handleSmartThingsPost 
router.post('/smartthings', smartThingsController.handleSmartThingsPost)

Again on the first request upon changing the route to /smartthings in the developers console I get a POST request to this route however the console does not display the confirmation url, instead it displays what is shown below.

2021-03-24T16:10:54.007Z error: Forbidden - failed verifySignature
2021-03-24T16:10:54.008Z error: Unauthorized
POST /smartthings 401 152.406 ms - 9

Printing out the body of the raw request received there is actually a confirmationUrl sent

{
  messageType: 'CONFIRMATION',
  confirmationData: {
    appId: 'XXXXX',
    confirmationUrl: <URL>
  }
}

Clicking on this <URL> does verify the smartapp it seems. It allows data from subscriptions to be sent from the ST cloud however the handleHttpCallback blocks these requests from calling my subscription handlers as it is again saying that the app is Unauthorized even though it is receiving data at that url.
Below is a print of the request body of data being received and the SmartThings SDK blocking it

DATA_RECEIEVED:
{
  messageType: 'EVENT',
  eventData: {
    installedApp: {
      installedAppId: 'XXXX',
      locationId: 'XXXX'
    },
    events: [ [Object] ]
  }
}
2021-03-24T16:37:22.147Z error: Forbidden - failed verifySignature
2021-03-24T16:37:22.147Z error: Unauthorized
POST /smartthings/ 401 2.286 ms - 9

The only route the SDK seems to work for is the root / URL which I cannot use. This couldn’t be correct is it? I cannot spot what I am doing wrong as I am getting data its just being blocked.
Any help is welcome!

The problem seems to occur in this block of code located in the authorizer.js file lines 170 to 189

	/**
	 * @param {any} req Incoming request
	 */
	async isAuthorized(req) {
		try {
			const keyResolver = this._keyResolver
			const parsed = httpSignature.parseRequest(req, undefined)
			const publicKey = await keyResolver.getKey(parsed.keyId)

			if (httpSignature.verifySignature(parsed, publicKey)) {
				return true
			}

			this._logger.error('Forbidden - failed verifySignature')
			return false
		} catch (error) {
			this._logger.exception(error)
			return false
		}
	}

It does not seem to want to verify the signature using httpSignature.verifySignature(parsed, publicKey) when I have the target url as a subdomain.

This works as target url - https://www.example.com
This does not work as target url - https://www.example.com/smartthings, it gets the Forbidden - failed verifySignature result from the snippet above.

Anyone have any idea why/spot something I have done wrong?
The appID, clientID and clientSecret have not changed.

Hi all,

I think it is a mixture of a few things that is causing this problem.

I dont have my express routes defined in one big server.js file like the sample and how a lot of the snippets are defined here.

In my server.js I just tell my express application where the routes are defined, in my case the routes folder and then at what path to use these for routes. Something like the snippet below.

var express        = require('express');
var smarthingsRouter    = require('./routes/smartthings')

// Instance of express 
var app = express();

// Routes 
app.use('/smartthings',smarthingsRouter);

For the files in the routes directory all they do is hand the req and res object off to controllers for validation. Below is a snippet from the file ./routes/smartthings

var express         = require('express');
var router          = express.Router();
var smartThingsController = require('../controllers/smarthings_controller')

/// POST home page. ///
router.post('/smartthings', smartThingsController.handleSmartThingsPost)

module.exports = router;

In the controller is any error handling of request. In the case of this route it is just calling smartapp.handleHttpCallback in the file ./controllers/smarthings_controller.

const handleSmartThingsPost = async function (req, res, next) {
    try{
        // Handle the http callback 
        let result = await smartapp.handleHttpCallback(req, res);
    }catch(error){
        logger.error(`Error in handling post request to \ ${error}`)
    }
}

To get around this I defined the route in the server.js file directly instead of in the routes directory.

// POST //
app.post('/smartthings',async (req, res) => {
	let result = await smartapp.handleHttpCallback(req, res)
})

There are a few differences when console logging the request object when they are defined in the two files defined.

One that jumps out is the req.route parameter.

req.route when defined in the server.js file

route: Route { path: '/smartthings', stack: [ [Layer] ], methods: { post: true } }

req.route when defined in the ./routes/smartthings.js file

route: Route { path: '/', stack: [ [Layer] ], methods: { post: true } }

For now I am going to just define the route in the server.js file but its not really common practice to do this as the server.js would get out of hand.

The express website gives a good example of spliting up routes with the express router. The express generator also generates projects in this format . For controller explanation can see here but most tutorials go through controllers.

Hi, @Warren1. I’ve discussed this with our engineering team and they saw there’s an open issue about this for the http-signature library used to verify the request.
The root cause is that the express Router is changing the requestUrl and http-signature uses that value instead of the located in originalUrl.
It seems it won’t be corrected based on the lack of activity there, so, they suggested applying the workaround provided there adapted to your case, eg.

router.post('/smartthings',  (req, res, next) => {
	req.url = req.originalUrl
	smartApp.handleHttpCallback(req, res);
})

Let me know your results. :smiley:

1 Like

@nayelyz,

Thank for this seems to be working now! :slight_smile:

1 Like