Your widget key and the ids in the page are visible to anyone who opens the page source. The page therefore also carries a signed identity pass, a short piece of text your server creates to prove which firm and adviser the page is for.
The widget sends the pass with every request. Subcontext refuses any request whose firm id or adviser id doesn't match the pass. This stops anyone editing the ids in the page to reach another firm's account, start a trial in its name or send messages from it.
Signed identity is required unless Subcontext has switched it off for your portal (only suitable for demo pages). If you do send a pass, it is always checked.
Creating the pass#
You need the key id (nhk_...) and the secret (nhs_...) from
Subcontext. Create a new pass each time you render the page. A pass must
expire within 24 hours (a minute of clock difference is allowed). One hour
is a good choice.
body = "v1." + key_id + "." + expires_unix + "." + b64url(firm_id) + "." + b64url(adviser_id)
token = body + "." + hex(hmac_sha256(secret, body))
expires_unixis the expiry time in whole seconds since 1 January 1970 (Unix time).b64urlis URL-safe base64 with no=padding.firm_idandadviser_idmust be exactly the values you put indata-firm-idanddata-advisor-id.- The signature is an HMAC-SHA256 of
bodyusing the secret, written as lower-case hex.
Node.js:
const crypto = require('node:crypto')
const b64 = (s) => Buffer.from(String(s)).toString('base64url')
const body = ['v1', keyId, Math.floor(Date.now() / 1000) + 3600, b64(firmId), b64(adviserId)].join('.')
const token = body + '.' + crypto.createHmac('sha256', secret).update(body).digest('hex')
Ruby:
require 'base64'
require 'openssl'
b64 = ->(s) { Base64.urlsafe_encode64(s.to_s, padding: false) }
body = [ "v1", key_id, (Time.now + 3600).to_i, b64.(firm_id), b64.(adviser_id) ].join(".")
token = "#{body}.#{OpenSSL::HMAC.hexdigest('SHA256', secret, body)}"
C#:
string B64(string s) => Convert.ToBase64String(Encoding.UTF8.GetBytes(s)).TrimEnd('=').Replace('+', '-').Replace('/', '_');
var body = $"v1.{keyId}.{DateTimeOffset.UtcNow.AddHours(1).ToUnixTimeSeconds()}.{B64(firmId)}.{B64(adviserId)}";
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var token = body + "." + Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(body))).ToLowerInvariant();
Then put the result in data-identity-token (or identityToken in
init()).
Keeping the secret safe#
- Keep the secret on your server. Never put it in the page or in browser code.
- More than one signing key can be active at once, so you can change keys without downtime. Ask Subcontext for a new key, deploy it, then ask them to switch off the old one.
- If the adviser changes in a single-page portal, fetch a new pass from your
server and pass it to
setConsumer(see setConsumer).