INTERNAL DEV - Salesforce JWT API Auth key / secret setup ( MODERN JWT)

INTERNAL DEV - Salesforce JWT API Auth key / secret setup ( MODERN JWT)

JWT-Based Salesforce API Integration - Internal (ActiFi Dev) Documentation

Internal engineering documentation for the AWS Secrets Manager side of the SuccessPro JWT API integration - the steps only ActiFi Dev performs. Companion to the client-facing "API User" setup doc (Permission Set / User / External Client App)



Full sequence (who does what)

The keypair is generated by ActiFi Dev, not the client - the client only ever receives and uploads the public certificate ActiFi sends them.
Order of Operations:
    (ActiFi Dev) Generate the keypair, send the .crt to the client admin. -> Step 1 below.
    (Client Admin) Create the Permission Set + API integration User. -> client-facing "API User" doc, steps 1-3.
    (Client Admin) Create/confirm the External Client App, upload the certificate ActiFi sent in step 1, enable JWT Bearer Flow, confirm scopes. -> client-facing doc, steps 4-6.
    (Client Admin) Send ActiFi Dev the Consumer Key / Secret. -> client-facing doc, step 7.
    (ActiFi Dev) Run the aws cli secret upsert script with the .key file (from step 1) and the Consumer Key (from step 4). -> Step 2 below.
    (ActiFi Dev) Verify every field in the resulting secret (AWS Console → Secrets Manager) stale values are the most common failure. -> Step 3 below.
    (ActiFi Dev) Confirm the JWT branch is actually taken, not a silent fallback. -> Step 4 below.

Step 1: Generate the keypair

A new, unique keypair per tenant - never reuse a keypair across tenants.
openssl req -x509 -sha256 -nodes -days 3650 -newkey rsa:2048 \
-keyout {tenant}_salesforce_api_integration.key \
-out {tenant}_salesforce_api_integration.crt
  • .crt (public certificate) - send to the client admin for upload to the External Client App (Edit Settings -> OAuth -> Flow Enablement -> Enable JWT Bearer Flow -> Certificate Upload). Never goes into Secrets Manager.
  • .key (private key) - never sent to Salesforce or the client, ever. Goes to Secrets Manager only (Step 2 below).

Step 2: Run the upsert script

roadmap-node/scripts/aws/upsert-aws-identity-secret.ts retrieves the existing ${tenant}_salesforce_api_creds secret, merges in values from local .key/.crt files, and saves it back (creates the secret if it doesn't exist, updates it otherwise - existing fields like sf_sync_username are preserved, not clobbered).
# Put the .key and .crt files (from Step 1) in the same directory as the script, then:
node --require ts-node/register --inspect-brk ./scripts/aws/upsert-aws-identity-secret.ts
Before running: edit the script's secretName and file path placeholders (currently [[TENANT]]) to the actual tenant name.
Known limitation: this script does NOT write sf_client_id. It only writes sf_private_key and sf_public_certificate. The Consumer Key (received from the client in step 4 of the sequence above) must be added separately - by hand-editing the AWS secret after running the script.

Step 3: Verify every field in the secret - do not assume pre-existing values are correct

This is the step most likely to be silently skipped, and the failure mode is silent, not loud. Most tenants already have some legacy ${tenant}_salesforce_api_creds secret in place - its pre-existing sf_client_id and sf_sync_username were almost certainly for something else and will NOT match this setup:
  • sf_client_id must match the Consumer Key of the exact External Client App the certificate was uploaded to. A stale value means the JWT branch authenticates against the wrong (or nonexistent) app.
  • sf_sync_username must be the exact Username of the integration user created for this setup (e.g. dev+{tenant}@actifi.com). The JWT assertion's sub claim uses this value directly - a stale username authenticates as the wrong user, or fails outright.
Full expected shape of ${tenant}_salesforce_api_creds (ISalesforceAPICreds, roadmap-node/src/interfaces/salesforce/interfaces.ts:46-55):
Field
Required for JWT?
Notes
sf_sync_username
Yes
Must match the integration user's actual Username; verify not stale
sf_client_id
Yes
Consumer Key of the target ECA; verify not stale; not written by the script
sf_private_key
Yes
Written by the script from the .key file
sf_public_certificate
No
Written by the script from the .crt file; not read by getSalesforceConnection(), harmless either way
sf_sync_login_url
Yes (both paths)
Org login URL, e.g. https://test.salesforce.com for a sandbox
sf_sync_password
Deliberately omitted for new JWT-first tenants
See below
sf_sync_sec_token
Deliberately omitted for new JWT-first tenants
See below
getSalesforceConnection() (roadmap-node/src/lib/salesforce-util.ts) takes the JWT Bearer path whenever both sf_client_id and sf_private_key are present; otherwise it falls back to legacy conn.login(username, password + securityToken) - the deprecated SOAP path being retired (SP-6386).
For a brand-new tenant onboarding straight to JWT, we deliberately leave sf_sync_password/ sf_sync_sec_token unpopulated. If the JWT fields are ever missing or wrong, the fallback should fail loudly rather than silently limp along on a stored legacy credential - that failure is the signal something's misconfigured, not a risk to guard against.
This is different for existing tenants mid-migration off legacy conn.login() - those may still have real sf_sync_password/sf_sync_sec_token values in place intentionally, as a safety net during transition. Don't strip those without a specific reason to.