This guide will outline all necessary steps to form a proper JWS. This example uses the ACH v2 API, but please note that this approach to forming the JWS is the same across all of the latest versioned US Payments APIs.
Step 1 : JWS Signature
All payment requests will need to be signed with a JWS signature. This step makes certain that no one tampered with the payload after the signature was generated.
SVB provides code samples in Python below to calculate the JWS signature. Please also see our Java example and GO example
Python code sample
Below Python and json files can be grouped and used to calculate the JWS signature token. When below files are grouped within a project, following command can be executed to retrieve the JWS signature:
- -f /detached-jwt-utility/payload.json -s clientSecret
- where the first parameter is the absolute path to the request payload (i.e. payload.json in this example) and the second parameter is the secret string received form the API support team (clientSecret).
detachedJWT.py
from jwcrypto import jwk, jwt from InputOpt import InputOption import uuid def main(): input = InputOption() InputOption.input() print(DetachedJWT.detached_jwt(input.get_filePath(), input.get_secret())) class DetachedJWT: @staticmethod def detached_jwt(filePath, secret): key = jwk.JWK.from_password(secret) # Reading the input payload to be signed file = open(filePath, "r") payload = file.read() # Generating headers with necessary claims headers = {"alg": "HS256", "kid": str(uuid.uuid4()), "typ": "JOSE"} # Creating the jwstoken and sign jwstoken = jwt.JWT(header=headers, claims=payload) jwstoken.make_signed_token(key) sig = jwstoken.serialize() # Detaching JWT splittedJWS = sig.split(".") splittedJWS[1] = "" print("printing the jws token") return '.'.join(splittedJWS) if __name__ == "__main__": main()
InputOpt.py
import getopt import sys class InputOption: @staticmethod def usage(): print("ToCreateDetachJET: python detachedJWT.py -f <filePath.json> -s <Secret key>") @staticmethod def input(): try: (opts, args) = getopt.getopt(sys.argv[1:], 'f:s:h', ['filePath=', 'secret=', 'help']) except getopt.GetoptError as err: print(err) sys.exit(2) if len(opts) != 0: for (o, a) in opts: if o in ('-h', '--help'): InputOption.usage() sys.exit() elif o in ('-f', '--filePath'): global filePath filePath = str(a) InputOption.set_filePath(filePath) elif o in ('-s', '--secret'): global secret secret = str(a) InputOption.set_secret(secret) else: InputOption.usage() sys.exit(2) else: InputOption.usage() sys.exit(2) def set_filePath(filePath): filePath = filePath def get_filePath(self): return filePath def set_secret(secret): secret = secret def get_secret(self): return secret
payload.json
{ "buy": { "currency_code": "EUR", "value": "100.00" }, "sell_currency": "USD", "tenor": "TOD" }
Step 2 : oAuth 2.0 Access Token
ACH payment requests leverage two input parameters for security: JWS signature and oAuth 2.0 access token.
An API request to /v1/security/oauth/token is submitted to retrieve an access token.
/token endpoint uses Basic Authorization with a username/password pair which represent the credentials received by the API Support/Implementations team.
Step 3 : Submitting an ACH payment request
API requests will use an authorization type: Bearer token : access token, where the access token represents the token returned from the /token endpoint in Step 2.
JWS signature token from Step 1 will be included as a header parameter:
Java JWS Code Sample
package com.svb.api.utility; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.keys.HmacKey; import java.util.UUID; public class DetachedJwt { /** * This method uses HS256 algorithm for enryption to create a detached JWS * @param payload: the body of your request. * @param secret: this is the client secret which is shared after onboarding, also used as encyption key * @return: This method returns a detached JWS. * @throws Exception */ public static String generateDetachedJwt(String payload, String secret) throws Exception { String detached_jwt = ""; try { JsonWebSignature jws = new JsonWebSignature(); // The body of your request. jws.setPayload(payload); // Setting random value for Kid jws.setKeyIdHeaderValue(UUID.randomUUID().toString()); // The type for the JWT jws.setHeader("typ", "JOSE"); // This is your client secret used to encrypt the payload jws.setKey(new HmacKey(secret.getBytes("UTF-8"))); // Setting algorithm to HS256 jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256); jws.setDoKeyValidation(false); // Get signed JWS String token = jws.getCompactSerialization(); // Detaching the body for the JWS String parts = token.substring(0, token.indexOf(".")); int posA = token.lastIndexOf("."); String part2 = token.substring(posA + 1); detached_jwt = (parts.concat("..")).concat(part2); } catch (Exception e) { throw new Exception("Unable to generate detached jwt at this time."); } return detached_jwt; } }
GO JWS Code Sample
package main import ( jose "github.com/dvsekhvalnov/jose2go" "github.com/google/uuid" "strings" ) func generateDetachedJwt(payload string, secret []byte) (string, error) { jwt, err := generateJwt(payload, secret) return detachJwt(jwt), err } func generateJwt(payload string, secret []byte) (string, error) { kid := jose.Header("kid", uuid.NewString()) typ := jose.Header("typ", "JOSE") return jose.Sign(payload, jose.HS256, secret, kid, typ) } func detachJwt(jwt string) string { splitted := strings.Split(jwt, ".") jwtPart1 := splitted[0] jwtPart2 := splitted[len(splitted)-1] return jwtPart1 + ".." + jwtPart2 }