Search code examples
shopware6shopware6-app

How to verify Shopware 6 App confirmation request correctly with Node.js


So I was diving into Shopware 6 app development and wanted create my own app along side the official docs.

However, I am more into developing the app with Node.js/Express instead of PHP. Nevertheless I felt inspired how the regirstration and confirmation requests are handled in the official Shopware AppBundle. In particular the RequestVerifier

My RequestVerifier module looks pretty similar, however, I have trouble verifying the confirmation post request correctly.

Currently I have the following:

import { Request } from 'express';
const crypto = require('crypto');

const SHOPWARE_APP_SIGNATURE_HEADER = 'shopware-app-signature';
const SHOPWARE_SHOP_SIGNATURE_HEADER = 'shopware-shop-signature';

function authenticateRegistrationRequest(req: Request, appSecret: string): void {
    const signature = getSignatureFromHeader(req, SHOPWARE_APP_SIGNATURE_HEADER);

    verifySignature(appSecret, buildValidationQuery(req), signature);
}

function getSignatureFromHeader(req: Request, headerName: string): string {
    const signatureHeader = req.get(headerName);

    if (!signatureHeader) {
        throw new Error('Signature is not present in request');
    }

    return signatureHeader;
}

function buildValidationQuery(req: Request): string {
    const queries = req.query;

    return `shop-id=${queries['shop-id']}&shop-url=${queries['shop-url']}&timestamp=${queries.timestamp}`;
}

function verifySignature(secret: string, message: string, signature: string): void {
    const hmac = crypto.createHmac('sha256', secret).update(message).digest('hex');

    if (hmac !== signature) {
        throw new Error('Signature could not be verified');
    }
}

function authenticatePostRequest(req: Request, shop: Shop): void {
    const signature = getSignatureFromHeader(req, SHOPWARE_SHOP_SIGNATURE_HEADER);

    // TODO: Verify Request body contents correctly
    verifySignature(shop.getShopSecret(), JSON.stringify(req.body), signature);
}

module.exports = {
    authenticateRegistrationRequest,
    authenticatePostRequest
};

The authenticateRegistrationRequest function verifies the signature correctly in the first place with the Node.js Crypto module. On the other hand the confirmation request can't be verified.

In AppBundle/src/Authentication/RequestVerifier.php line 38 the $request->getBody()->getContents() is passed, which should be equal to JSON.stringify(req.body) in Node.js

The passed params in my verifySignature(shop.getShopSecret(), JSON.stringify(req.body), signature); have the following contents:

shop.getShopSecret(): 76d73cb7094d91a2a8ccb7eba5f30b649339879c : string

JSON.stringify(req.body): {"apiKey":"SWIAAGRXVXZVVTDHRWHYEWDRQG","secretKey":"dUpNY3ZjbzNKUFJIVE40allqcnl4bHFQV0lyVjhBRWJvNzJhZ2o","timestamp":"1669233633","shopUrl":"http://localhost:8888","shopId":"HBUXtMoPbXmKzkNq"} : string

signature: 0597c71bda40fa25d0f4edff3c54eedbfb26bc95cef202c8f9f03bb7c5fdc478 : string

What am I doing wrong here?


SOLUTION:

It depends on how you receive the req.body in express. The express body parser wasn't configured to deliver the raw request body in my case. So I decided to escape the forward slashes manually:

The following did the job:

verifySignature(shop.getShopSecret(), JSON.stringify(req.body).replace(/\//g, '\\/'), signature);

My body parser looks the following:

import express from 'express';

const app = express();

app.use(express.json());

Solution

  • At first glance everything looked correct, so I took the outputs you provided and experimented in PHP.

    $hmac = hash_hmac('sha256', '{"apiKey":"SWIAAGRXVXZVVTDHRWHYEWDRQG","secretKey":"dUpNY3ZjbzNKUFJIVE40allqcnl4bHFQV0lyVjhBRWJvNzJhZ2o","timestamp":"1669233633","shopUrl":"http://localhost:8888","shopId":"HBUXtMoPbXmKzkNq"}', '76d73cb7094d91a2a8ccb7eba5f30b649339879c');
    $signature = '0597c71bda40fa25d0f4edff3c54eedbfb26bc95cef202c8f9f03bb7c5fdc478';
    
    hash_equals($hmac, $signature);
    // false
    

    So I was able to reproduce the issue.

    Then I remembered that PHP's json_encode does escape forward slashes by default.

    $hmac = hash_hmac('sha256', '{"apiKey":"SWIAAGRXVXZVVTDHRWHYEWDRQG","secretKey":"dUpNY3ZjbzNKUFJIVE40allqcnl4bHFQV0lyVjhBRWJvNzJhZ2o","timestamp":"1669233633","shopUrl":"http:\/\/localhost:8888","shopId":"HBUXtMoPbXmKzkNq"}', '76d73cb7094d91a2a8ccb7eba5f30b649339879c');
    
    hash_equals($hmac, $signature);
    // true
    

    Looks like the unescaped slashes are the cause of the issue. I'm not super familiar with express but if you can just pass the raw request body. If that is not possible stringify and escape the forward slashes.