In your Instnt JavaScript agent workflow configuration, you will configure the public part of a Public-Private RSA keypair. The private part of the keypair, which only you possess, is required to decrypt the data. The security of the keys is ensured by signing them with RSASSA-PKCS1-v1_5 SHA256 and encrypting them with RSA-OAEP for key wrapping and AES GCM for payload encryption. It's important to note that both of these processes require a minimum key length of 2048 bytes.
Generate Keys
To begin, we recommend generating a 4096-bit RSA key pair, aptly named 'private.pem.' This private key is further encrypted using the AES256 cipher:
% openssl genrsa -aes256 -out private.pem 4096
Generating RSA private key, 4096 bit long modulus
.............................................................
..............................................................++
..........................................................................................++
e is 65537 (0x10001)
Enter pass phrase for private.pem:
Verifying - Enter pass phrase for private.pem:
During this process, you will be prompted to enter and verify a passphrase for additional security.
Public Key
For creating a workflow, you'll need to extract the public key from the encrypted bundle generated earlier. This public key should be in Privacy Enhanced Mail (PEM) format, which is essentially a Base64 encoded DER certificate. PEM certificates are commonly used for web servers due to their readability with a simple text editor.
To extract the public key from 'private.pem' in PEM format, use the following command:
% openssl rsa -in private.pem -outform PEM -pubout -out public.pem
You'll be required to enter the passphrase for 'private.pem' during this process. The resulting 'public.pem' file should include headers and footers to be used for configuration.
% cat public.pem
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAySvoxZG9dKFjV/7Jfg27
cZKdUr7sfeeziwnZXMGLkvsRZldxNdWPMkgr/UvgQuov5TlTXG8wddN9YJGDsFQt
F9xzEgC9uAZ8LZkP4nyRm41NyHSwaX+8ANy1X0Xngsqmi3EqF6mCfj733lGq24uH
GwfZ3NGtzFeXthTSTHvOuFpnVX2ci2l4XEEU0R/qowWJ76aJAgEpquMEazzY6Vff
Sxyw9rBDS5YNbL/i6JlTNUElVY2A+CkYZ/dILe+iod/lXyiB6Y0nKfqpExK9t3RJ
gxXcxOcquABIuMMAtKPe/jQElHTX5xkCPdkPwmabcTj+eOUY4EdCf0T9nfB7M6Fg
TDvFAvm0xYo/T4pJ22kO1I6x5dT+u12ZNueDQfLVlKFi4381/9f/vDCn0hi7a1EL
CxiQqGnK6XBz06oB+zfYdButSArn14K81cXr/ETAxgN9SgjjWSecaFfktcI4oQdD
7FaCQwZX6C8gMUMQX/j2Xn+n9pPXBvyCfgAa4fgBRzU4SAIH4StVrz+lv6b8zXI0
taDKGjwDNrcjJKzBGYs3RvhzSuDwKqI037/k1+nx/J53SZ1uqRx+OrqfSK2FXqVn
xzE6iuRo8cCFDVDVVCku6ZO/n9qAH6InGtRu1S4gRM/0txLa8whZKzv+/vdD1QhA
5M8HKZ7bKCLkRxTlZasv6rsCAwEAAQ==
-----END PUBLIC KEY-----
Configuration and Workflow
To properly configure your workflow, copy and paste the complete 'public.pem' key, including the "BEGIN PUBLIC KEY'' and "END PUBLIC KEY" headers and footers, into the Encryption Management section located in the Security pane of Instnt's workflow builder. This step ensures that your data remains secure and accessible only to authorized parties.
Decrypting and Verifying the JWE Token
After a potential customer submits their information through your website's workflow fields, your business will receive onboarding decisions and assertion details in a secure JWT container format. To access this information, you must decrypt and validate the container. The JWT specification is supported by several programming languages, such as Python, Node.js, and Java.
To decrypt the JWE token, your business' private PKI key is used in the first step. In the second step, the JWS token signature enclosed in the container is verified as being issued by Instnt. This verification process uses the public portion of the Instnt signing key which is shared with you:
-
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvZmtK56bWzsQiCnXysb9
qCRgIW0NSVtZUmlOyVQSYndsM01fExznujGn4I9mHVjuecnObQckklzCeq3VbfIr
SmcCUyNegoiTzBUjJ9XuIXjnDUTeoCzTtDuOLdgespYQ/mAnO+EDa2tKiZiSUkNZ
tZ40XyHgd5w9L58FhzSX64LjduWYpiDXLAUQtWFOixkrWjAV4/WSqZdEfKf3kkrz
P7cIfuJwkg8EmcQwJ8uMbUFyvgeoPghdhcpOZV8uIf91rrhWaJMxQqXb7RafoFIM
dlvwE2GAgyMHxHcGKlPq5AJucaiuYzSzp8rSyTj5+Z841jmqpxk6WFuV6T9yk5a7
8KPZz8mwjkEvZZQN/0sbPEH9skH+VK+DrTsIOQkUmu/dCcS78M///TnfCWNGvsxh
7N6xz9PAVjXuegj2tqLUuhnGiZKWovQP+teSnEeZT17T+Fg1Yt4uhHNNCTlxESX6
BNH1Iq1WR99SScVjCrNQ0G9VewcGH1Ym7qFrLzihYAMmMF4orGO9slrD2kZN/FPs
vd4Xe3qQuzC7McFiFCb8xMvsaVi+u2Ogy2Q775hXWpwcrcKgm9Iky7vA2k/qbRLe
CQoHHgWOZiypX1ZfBYV98dI3kMfsh6TYOTmJXw3P4wij7DsKcKhCPLDnAIKqBkyz
Q1TldZKTWJJxtqDC525oCVECAwEAAQ==
-----END PUBLIC KEY----- -
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyxnDuCjaGcK0QLWx5m4d
cNfalWJp2shGHV32NReGxqw1JM+toXDK6cqvmUomZ9DG6bVembw2hDKPTajsQwJO
nrM6D0KfNiAxi2Mm01nQ7a9+39/l0uSxBMHLVk9e9w3GHiCz/E7cR1b8myrnghoW
lMKUmMhTOCDANglf3LtIv4Tj13V8Si/Rj+AoBU/st8g6e6OnpZbqjOfSb9VS+3PI
TrM93drjzW+ZE2L/tyF1pM6f+OavBDUtKwAqguXB7RU2JMp8hNw7ehSoqx1vFmqL
q6gKwURfsjIwh2dVNMm+3Icr5ZalH188/0iwPlu5zC9bknTG1K8gPVHs8LlFRAxJ
asIlTOrGSk4v5Qaf4wskqCBlA5aFvoQzDfJhMgnN0x9eHocY6MmztEMViOJl9XyO
+aEdAfaVqrHstBiT/r6FBe5/mCR4I+fl+GdO6AxCOIJ0ZKUTkbWPVy6jjOvWFm7z
INfqZas9IQ7rvQv0a/hvs03pewToEwVycbgdlfMJ2B62jR+EDFYpzJnOwvuTLcZE
SX2tlf1bGPiuoJD+IJKvAb9Mdf90ZTMuldiEdLnPcjC13ITxYrPfuENfYLs6fVMl
lxn1qUVnyngqzUSKu6c6D1WVkSB2modtyUNbAMEEyf3UY7gaPL5TciRktltK2Zi7
twXuhzM/UCTR2XXNaFmdMyECAwEAAQ==
-----END PUBLIC KEY-----
To resume the onboarding workflow after receiving the Instnt decision callback, you need to retrieve the user assertion from the Instnt Assertion API by using the instntid UUID reference provided in the Decision Callback payload. Instnt will provide a reference with a redirect URL for you to proceed with the next step in your business' onboarding process.
Before accessing the instntid, the instntjwt token must be decrypted. The instntid is then used as an input for the GetAssertion function, as described in the Analyzing the Assertion Response documentation.
Here are some decryption and verification examples written in Python and Node.js frameworks:
-
from authlib.jose import JsonWebEncryption
from authlib.jose import JsonWebSignature
from authlib.jose import JWE_ALGORITHMS
# JWE token
JWE = 'eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.Tst5foFWOBfhGiKSBse3R3...'
# Read customer private key
with open('private.pem', 'r') as f:
private_key = f.read()
# Decrypt JWE
jwe = JsonWebEncryption(algorithms=JWE_ALGORITHMS)
jwe_decrypted = jwe.deserialize_compact(JWE, private_key)
# Read Instnt public key
with open('instnt_kms_public.pem', 'r') as file:
instnt_kms_public_pem = file.read()
# Validate JWS
jws = JsonWebSignature(algorithms=['RS256'])
assertion = jws.deserialize_compact(jwe_decrypted['payload'], instnt_kms_public_pem)
json.loads(assertion['payload'].decode('utf-8'))
# {'iss': 'https://instnt.org', 'aud': 'https://acmebank.org', ..., 'instntid': '90205512-586d-4637-86cd-b63b582e480c'} -
var jose = require('node-jose');
var fs = require('fs');
// JWE token
var JWE = 'eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.Tst5foFWOBfhGiKSBse3R3...'
// Read customer private key
var private_pem = fs.readFileSync('private.pem', 'utf8');
var private_key;
jose.JWK.asKey(private_pem, 'pem').
then(function(result) {
private_key = result;
});
// Decrypt JWE
var jwe_decrypted;
jose.JWE.createDecrypt(private_key).
decrypt(jwe).
then(function(result) {
jwe_decrypted = result;
});
// Read Instnt public key
var instnt_kms_public_pem = fs.readFileSync('instnt_kms_public.pem', 'utf8');
var instnt_public_key;
jose.JWK.asKey(instnt_kms_public_pem, 'pem').
then(function(result) {
instnt_public_key = result;
});
// Validate JWS
var assertion;
jose.JWS.createVerify(instnt_public_key).
verify(jws).
then(function(result) {
assertion = result;
});
assertion.payload.toString()
// {'iss': 'https://instnt.org', 'aud': 'https://acmebank.org', ..., 'instntid': '90205512-586d-4637-86cd-b63b582e480c'} -
import com.nimbusds.jose.crypto.RSADecrypter;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.EncryptedJWT;
import com.nimbusds.jwt.SignedJWT;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
public class JWTDecryptVerify
{
public static void main(String[] args) throws Exception
{
String jwe = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ....";
// Customer private encryption key
String private_pem = new String(Files.readAllBytes(Paths.get("private.pem")),
StandardCharsets.UTF_8);
JWK private_key = JWK.parseFromPEMEncodedObjects(private_pem);
RSADecrypter decrypter = new RSADecrypter((RSAKey) private_key);
// Decrypt
EncryptedJWT jweObject = EncryptedJWT.parse(jwe);
jweObject.decrypt(decrypter);
// Instnt public signing key
String instnt_kms_public_pem = new String(Files.readAllBytes(Paths.get("instnt_kms_public.pem")),
StandardCharsets.UTF_8);
JWK instnt_kms_public_key = JWK.parseFromPEMEncodedObjects(instnt_kms_public_pem);
JWSVerifier verifier = new RSASSAVerifier((RSAKey) instnt_kms_public_key);
// Verify
SignedJWT signedJWT = jweObject.getPayload().toSignedJWT();
if (signedJWT.verify(verifier)) {
System.out.println(signedJWT.getJWTClaimsSet());
} else {
System.err.println("Not verified!");
}
}
}
The token format and unwrapping process remain the same for both the assertion reference on the callback and the assertion itself, as explained in the Analyzing the Assertion Response documentation.