JWT Configuration for SnapLogic Public API
This document details the process of configuring JWT authentication for the SnapLogic Public API using self-generated keys without the use of any third party JWT providers. It covers key generation, JWKS creation, SnapLogic configuration. 1. Key Generation and JWKS Creation 1.1 Setup the CMD Open CMD Mount the OpenSSL bin folder 1.2 Generate the Private Key Use the following command to generate a 2048-bit RSA private key in the PEM format. BASH openssl genpkey -algorithm RSA -out jwt_private_key.pem -pkeyopt rsa_keygen_bits:2048 Result:A file named jwt_private_key.pem will be created. This key must be kept secret and secure. 1.3 Convert to PKCS#8 Format The JWT generation requires the private key to be in the PKCS#8 format for proper decoding. So, convert the jwt_private_key.pem into PKCS8 format. BASH openssl pkcs8 -topk8 -in jwt_private_key.pem -out jwt_private_key_pkcs8.pem -nocrypt Result:A new file, jwt_private_key_pkcs8.pem, will be created. Use this key in your application for signing JWTs. 1.4 Extract the Public Key The public key is required for the JWKS document. BASH openssl rsa -in jwt_private_key_pkcs8.pem -pubout -out jwt_public_key_pkcs8.pem Result:A file named jwt_public_key.pem will be created. 1.5 Extract Public Key Components for JWKS: Extract the Modulus and Exponent from the CA-signed public key. These are the core components of your JWKS. BASH openssl rsa -pubin -in jwt_public_key_pkcs8.pem -text -noout The output will look like this:Public-Key: (2048 bit)Modulus: 00:d2:e3:23:2c:15:a6:5b:54:c1:89:f7:5f:41:bf:...Exponent: 65537 (0x10001) 2. JWKS Creation and JWT Endpoint Configuration 2.1. The below steps explain how to create the JWKS JSON within Snaplogic. 2.1.1 Create a new project sapce and a project "JWKS" or even an API with name "JWKS" - (This step is just for access control and the API policy to be applied only for this purpose) 2.1.2 Create the pipeline CreateJWKS 2.1.3 Update the Modulus and Exponent values in the mapper copied from the step 1.5 in the section Key Generation, JWKS Creation, and Certificate Signing. 2.1.4 Select the language as Python and replace the default script in the script snap with # Import the interface required by the Script snap. from com.snaplogic.scripting.language import ScriptHook import base64 import hashlib class TransformScript(ScriptHook): def __init__(self, input, output, error, log): self.input = input self.output = output self.error = error self.log = log # Helper function to convert an integer to a big-endian byte string # This is a manual implementation of int.to_bytes() for Python 2.7 def int_to_bytes(self, n): if n == 0: return '\x00' hex_string = "%x" % n if len(hex_string) % 2 == 1: hex_string = '0' + hex_string return hex_string.decode("hex") def execute(self): self.log.info("Executing Transform script") while self.input.hasNext(): try: inDoc = self.input.next() # Modulus conversion logic hex_input = inDoc['hex_string_field'] clean_hex_string = hex_input.replace('\n', '').replace(' ', '').replace(':', '') modulus_bytes = clean_hex_string.decode("hex") modulus_base64url = base64.urlsafe_b64encode(modulus_bytes).rstrip('=') # Exponent conversion logic exponent_input_str = inDoc['exponent_field'] import re match = re.search(r'^\d+', exponent_input_str) if match: exponent_int = int(match.group(0)) else: raise ValueError("Could not parse exponent value from string.") exponent_bytes = self.int_to_bytes(exponent_int) exponent_base64url = base64.urlsafe_b64encode(exponent_bytes).rstrip('=') # Dynamic Key ID (kid) generation logic # Concatenate the Base64url-encoded modulus and exponent jwk_string = modulus_base64url + exponent_base64url # Compute the SHA-256 hash kid_hash = hashlib.sha256(jwk_string).digest() # Base64url encode the hash to create the kid kid = base64.urlsafe_b64encode(kid_hash).rstrip('=') # Prepare the output document with all values outDoc = { 'modulus_base64url': modulus_base64url, 'exponent_base64url': exponent_base64url, 'kid': kid } self.output.write(inDoc, outDoc) except Exception as e: errDoc = { 'error' : str(e) } self.log.error("Error in python script: " + str(e)) self.error.write(errDoc) self.log.info("Script executed") def cleanup(self): self.log.info("Cleaning up") hook = TransformScript(input, output, error, log) 2.1.5 Replace the default value in the JSON generator with { "keys": [ { "kty": "RSA", "alg": "RS256", "kid": $kid, "use": "sig", "e": $exponent_base64url, "n": $modulus_base64url } ] } This will return us the JWKS JSON. 2.2. The below step creates the public endpoint for the JWKS JSON. The below steps can be done as a standalone API as well as a separate project for this JWKS authentication. 2.2.1 Create the pipeline getJWKS 2.2.2 Paste the JWKS generated in step 2.1.5 above in the JSON Generator: { "keys": [ { "kty": "RSA", "alg": "RS256", "kid": "vTfx70NbtVbarHnBetDHNqLXsWVr4Ue5oC32TFNSMlc", "use": "sig", "e": "AQAB", "n": "ANLjIywVpltUwYn3X0G_********_3JmpnSh419wDZC_8-Ts" } ] } 2.2.3 Follow the config as shown for JSON Formatter: 2.2.4 Create a Task named jwks.json and follow the task config as shown and copy the Ultra Task HTTP Endpoint: Select the Snaplex as Cloud, as the endpoint have to be truly public. 2.2.5 Create an API Policy - Anonymous Authenticator and key in the details as shown: 2.2.6 Create an API Policy - Authorize By Role and key in the details as shown: 3. SnapLogic JWT Configuration This step links SnapLogic to your JWKS. Configure Admin Manager: 3.1 In the SnapLogic Admin Manager, navigate to Authentication > JWT. 3.1.1 Issuer ID: Enter a unique identifier for your issuer. This can be a custom string. 3.1.2 JWKS Endpoint: Enter the full HTTPS URL where you have hosted the JWKS JSON file, HTTP Endpoint copied from step B.4 in the Section JWKS Creation and JWT Endpoint Configuration. 3.2 In the SnapLogic Admin Manager, navigate to Allowlists > CORS allowlist 3.2.1 Add domain: Key in the domain https://*.snaplogic.com in the Domain text box, click on Add Domain and click on Save. 4. JWT Generation and Structure The JWT must be created with a header that references your custom kid and a payload with claims that match SnapLogic's requirements. 4.1 Header: JSON { "alg": "RS256", "typ": "JWT", "kid": "use the key id generated in step 2.1.5 from the section JWKS Creation and JWT Endpoint Configuration"} 4.2 Payload: JSON { "iat": {{timestampIAT}}, "exp": {{timestampEXP}}, "sub": "youremail@yourcompany.com", "aud": "https://elastic.snaplogic.com/api/1/rest/public", "iss": "issuer id given in section 3.1.1.1", "org": "Your Snaplogic Org" } 4.3 Sign the JWT: Use the jwt_private_key_pkcs8.pem to sign the token with your application's JWT library. 4.4 Postman Pre-Request script to automatically generate epoch timestamps for iat and exp claims let now = new Date().getTime(); let iat = (now/1000) let futureTime = now + (3600 * 1000); let exp = (futureTime/1000) // Set the collection variable pm.collectionVariables.set("timestampIAT", iat); pm.collectionVariables.set("timestampEXP", exp);Tutorial: Using the DocuSign eSignature REST API with the REST Snap Pack and JWT Authentication
I previously wrote about integrating with the DocuSign eSignature API with the REST Snap Pack and OAuth 2.0 authentication, but for those that wish to control their access a bit more, DocuSign supports authenticating with JSON Web Tokens (JWTs) too. I followed the instructions from the “How to get an access token with JWT Grant authentication” DocuSign Developer portal. Setup As before, DocuSign makes it very easy to get started with their free Developer Account signup. Again, in the sidebar under “INTEGRATIONS”, select “Apps and Keys”. One that page is up, choose the “Add App & Integration Key” button and provide an application name (I chose “SnapLogic Community JWT Demo” this time). The “Integration Key” that is generated is also known as the Client ID and JWT will also refer to it as the "iss" value. You can keep the User Application selected as Authorization Code Grant but instead of adding a Secret Key, this time we’re going to be choosing a Service Integration: I wanted DocuSign to generate the public/private key pair for me, so I selected the “Generate RSA” button. A new dialog will open this the generated keys - you must copy these values and store them somewhere as this will be your only chance to do so: Finally, I set a throwaway Redirect URI, http://localhost (this isn’t really used - the goal here is to first authorized the app you created but JWT will take over from that point). Click “Save” to create the Integration App. Grant Consent The first thing you want to do is to get consent from a DocuSign user (it could be you) for this App to impersonate them (that’s what a Service account does). It follows this URI syntax: https://account-d.docusign.com/oauth/auth? response_type=code &scope=YOUR_REQUESTED_SCOPES &client_id=YOUR_INTEGRATION_KEY &state=YOUR_CUSTOM_STATE &redirect_uri=YOUR_REDIRECT_URI so I opened the following URL in my browser (substitute your client ID/integration key, state, scopes and redirect URI that you wish to use): https://account-d.docusign.com/oauth/auth?response_type=code&scope=signature%20impersonation&client_id=edec7e1e-e642-451b-8dfd-e7a211b23b40&state=throwaway&redirect_uri=http://localhost You’ll be asked to consent to the application: and then redirected to that throwaway localhost redirect URI (you don’t need to save anything from this response). Generating the JWT Now that consent has been granted, it’s time to move to JWT. This is where the JWT Snap Pack comes into play. It’s outside the scope of this topic to fully describe JWT, but in short it is a token value that encodes header and payload/body data that is signed with keys. The DocuSign Developer docs take you through how its constructed. The JWT Generate Snap will take care of generated the correct header ( "kid" is the Key Alias ID - more on that later, "alg" has to be "RS256" , and "typ":"JWT" is implied by the JWT spec). The main action is to generate the JWT Payload/Body that matches what DocuSign wants: { "iss": "5c2b8d7e-xxxx-xxxx-xxxx-cda8a50dd73f", "sub": "464f7988-xxxx-xxxx-xxxx-781ee556ab7a", "aud": "account-d.docusign.com", "iat": 1598383123, "exp": 1598390123, "scope": "signature impersonation" } and there are few ways to do that. You could build the above JSON object manually than use it directly in the “Custom Metadata” section, but I’ll show how to leverage the various fields in the JWT Generate Snap and the JWT Account. "aud" and "sub" stand for Audience and Subject respectively and can be set directly on the JWT Generate Snap: "sub" is the most difficult value to get - in fact, you have to look it up by using the API to call the /userinfo endpoint and that means using the OAuth 2.0 flow. Luckily, my first post described exactly how to do this - follow the setup instructions and see the “GET User Info” section. Note: "aud" must be list of strings (hence the array). It will be disregarded silently if it is not and your auth will fail. We will investigate a usability enhancement here. The "iat" value will be generated automatically (it defaults to “now” in Unix Epoch timestamp format). "iss" is the issuer, and this will be the Client ID/Integration Key created when you registered your App in the DocuSign Developer portal. You configure the "iss" and "exp" values by creating a JWT Account and leveraging the “JWT Issuer” and “Token TTL” fields respectively (the latter is added to the generated "iat" value). That leaves the "scopes" (which will always include impersonation and most DocuSign APIs want the signature scope too, so I’ve include them both, space-separated), and that can be directly configured on the “Custom Metadata” field in the JWT Generate Snap. All this configuration between the Snap and Account settings will be combined to form the Payload (you may also see a "nbf" payload field automatically added, which is “not before” and defaults to 120 seconds before "iat" and “Token ID” becomes "jti" ; DocuSign will ignore these): Finally, we need to configure the JWT Generate Snap’s Account for signing. This is where the public/private keypair saved earlier will be used. We’ll want to create a protected keystore for use by the account. There are a variety of ways to do this, but I’ll demonstrate with openssl . The first step is to combine the public and private keys together into one PEM file: Then we’ll use openssl to create a X.509 certificate: And then we’ll combine the certificate and the private key in the PEM file to create a PKCS 12 (.p12) file format: Upload the .p12 file to the Account’s “Key Store” field, also providing the password you used to protect it. You can use the suggest bubble on the “Key Alias” field to refer to your entry by name (this will be the "kid" value in the JWT Header). Decoding the Generated JWT The output of the JWT Generate Snap will be the JWT token under the "access_token" field (don’t confuse this with a DocuSign API access token, that comes later): You can copy this value and use jwt.io to see the decoded values: Exchanging the JWT for a DocuSign API Access Token Alright, it’s finally time to actually get an access token from DocuSign and start using their API! We are going to use the REST POST Snap to send a form-encoded request to the DocuSign token endpoint: The changes are minimal - the HTTP Entity is "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion="+$access_token (using the JWT you just created) and you need to set the appropriate Content-Type HTTP Header value of application/x-www-form-urlencoded . The Service URL is https://account-d.docusign.com/oauth/token and note that this needs to match the Audience value you used earlier (it is also the Demo environment value - Production is different; see DocuSign’s docs linked above). If everything has been configured correctly, you’ll get an API access token: And like in the first post, you can use this token with the REST Snap Pack (e.g. GET), albeit this time setting it directly via the Authorization HTTP Header: and it should return data successfully: In the end, this pipeline looked simply like this: Cheers!8.1KViews2likes1Comment