hmac
based authentication, similar to its session token counterpart for embedded apps, is the means of associating initial requests to open your application (from Mantle) with more traditional forms of authentication (cookies). Since non-embedded applications are not hosted in an embedded iframe, there are no 3rd party cookie concerns.
When a user opens your non-embedded extension in Mantle, your application will receive a request that looks something like this:
https://example.com/api?timestamp=1609459200&organizationId=org123&userId=user456&hmac=abcdef123456
To verify the hmac
:
- Extract the
hmac
parameter from the list of parameters - Sort the remaining parameters alphabetically by key into a string:
organizationId=org123×tamp=1609459200&userId=user456
- Optionally (encouraged!) verify the
timestamp
parameter to make sure it is recent (within a minute or an hour) to prevent potential replay attacks. - Prepend the
timestamp
to the sorted parameters string payload:
1609459200.organizationId=org123×tamp=1609459200&userId=user456
- Verify the payload with your shared extension
secret
and assert that thehmac
parameter provided by Mantle matches your own generated one - If they match, you can trust assert that the parameters were signed by Mantle, and you can trust the
userId
andorganizationId
params as a way of associating this request with a user account. Theuser
andorganization
records would have been fetched by your application after completing the oauth flow
Node.js example
import { createHmac, timingSafeEqual } from "crypto";
const { hmac, ...otherParams } = request.query;
const { timestamp } = otherParams;
const sortedSignedParams = Object.keys(otherParams)
.sort()
.map((k) => `${k}=${otherParams[k]}`)
.join("&");
// TODO: verify timestamp
const hmacPayload = `${timestamp}.${sortedSignedParams}`;
const _hmac = createHmac("sha256", process.env.MANTLE_EXT_SECRET);
_hmac.update(hmacPayload, "utf8");
const calculatedHmac = _hmac.digest("hex");
if (!timingSafeEqual(Buffer.from(calculatedHmac), Buffer.from(hmac))) {
logger.info(
{
hmac,
calculatedHmac,
hmacPayload,
},
"Invalid HMAC"
);
return reply.status(403).send({ success: false, error: "Invalid HMAC" });
}
const { organizationId, userId } = request.query;
// ... proceed with authenticated request