This blog post describes how to authenticate at
Docusign, which is required for using the REST APIs from within an iFlow in
SAP Cloud Integration (aka CPI).
This post is based on
🔸SAP Cloud Integration
🔸OAuth 2.0
🔸Groovy
🔸DocuSign
Quicklinks:
Quickguide
Sample Code
For Achutha
Content
0.1.Prerequisites
0.2.Preparation
0.3. Introduction
1. DocuSign: OAuth Client
2. DocuSign: Consent
3. DocuSign: Libraries
4. CPI: Private Key
5. CPI: iFlow
6. CPI: Libraries
7. Run Scenario
Appendix
1: Groovy Script
Appendix
2: Maven pom
Appendix
3: Manually configure libraries
0.1. Prerequisites
- Access to a Cloud Integration tenant.
- Docusign: To follow this blog post, it is not required to have a productive account at Docusign.
- Optional: Familiar with Java, although the tutorial can be followed without local Java installation.
- Optional: it is an advantage to have maven installed
0.2. Preparation
To prepare for following this blog post, we create a free developer account at Docusign.
Such a test account which runs on a sandbox only allows for 3 signatures in lifetime.
Thus I recommend to be careful when testing.
During this tutorial, however, we won’t be executing an end-to-end scenario, our focus is on authentication only. So we won't use one of the 3 free signatures.
To create a test account, we start from here:
https://go.docusign.com/sandbox/productshot
0.3. Introduction
DocuSign provides a rich and powerful set of
REST APIs for multiple purposes - we’re not going to discuss features or possible scenarios today.
The focus is on: how to authenticate from within an iFlow.
The challenge:
The APIs are protected with OAuth.
Their Authorization Server supports 3 OAuth flows:
🔸Authorization Code
🔸JWT Grant
🔸Implicit
So everything looks normal, as expected:
To call an API, we just fetch a JWT token, then use it to call the API.
Coming to CPI:
CPI supports “Authorization Code” for outbound requests.
Means, in the dashboard, we (as admin) can create a “Security Artifact” based on Authorization Code.
This feature processes the OAuth flow with one-time user-login, then it uses refresh-token to fetch new JWT tokens without user-login (until expiration).
Thus making it usable for iFlows.
However, there are some preconditions regarding the refresh mechanism for the refresh token.
So first of all: what is a refresh token?
Even before the first-of-all, let’s remember the usual access flow:
The end user opens a web app,
which contacts the Authorization Server,
which asks the user for login,
then sends a code to the redirect URL,
which uses the code the request a JWT token
which is then used to access data from the Resource Server
OK.
When the end user accesses the app again after some time, the (short-lived) access token has expired and the process has to be repeated.
Which makes re-entering credentials for the user a tedious repeated task.
To circumvent the situation, the Authorization Server issues a (long-lived) refresh token along with the access token.
This refresh token can be used to fetch a new access token.
In such case, the grant type
refresh_token is used, and the token itself is passed along with the token-request.
Example:
authentication/oauth/token?grant_type=refresh_token&refresh_token=1x2y3z&client_id...
The response is the same as usual, when requesting a token:
It contains a new access token and a refresh token.
Differences:
At this point, different behaviors of different Authorization Servers are possible.
e.g.
- whenever a refresh token is used, the refresh token in the response will be new
- alternatively, if the old refresh token is still valid, it is included in the response
- alternatively, if the old refresh token is still valid, no refresh token is included in the response
- if a new refresh token is issued, the old one can be invalidated – or it can remain valid until it expires
- expiration time of refresh token can be short up to endless
Coming to CPI:
Such different behaviors shouldn't cause problems, usually.
However, at CPI, there are constraints, because CPI is not a user-centric web application.
E.g.
CPI requires that a refresh token is not invalidated after usage.
Please refer to
documentation for a full list of constraints.
Coming to the problem:
However, at Docusign, the refresh token is updated with each access token request, and the old refresh token is invalidated immediately.
As a consequence, the "Authorization Code" Security artifact cannot be used for automatically fetching token for DocuSign from CPI.
OMG
Nevertheless, I hope that all this introduction was interesting anyways…
So what now?
DocuSign supports the OAuth flow “JWT Grant” which is meant for “service integration” where an interactive user with browser is not available (see
here).
This means, there’s no user who would log in. Instead, a user-ID is used to generate a JWT token for that absent user.
The Authorization Server validates that generated JWT token and issues a new JWT token that can be used to call the DocuSign APIs.
This description shows that such a scenario can run in an automated flow, e.g. iFlow in CPI.
There’s only one requirement:
the real user, which is represented by the mentioned user-ID, must grant his
consent.
Means, must agree that the iFlow calls DocuSign APIs in his name, with his user-ID, and sends documents to be signed, for instance.
This is required only once, DocuSign server will remember it.
The modelling of requesting and granting consent cannot be part of this tutorial, as it requires an enterprise account at DocuSign.
As such, I have to ask you to go through the
documentation and
this blog post.
Some more info:
Choose OAuth type.
And what’s next?
So now that we’ve found the suitable OAuth flow for authentication for DocuSign APIs, we have to realize that there’s no built-in support for it in CPI.
It doesn’t matter, we can implement it manually, in a groovy script that can be embedded in an iFlow.
And there's a blog post (this blog post) which shows how to realize it.
👍
That groovy script does nothing than fetch a JWT token from DocuSign server.
The consent has to be granted beforehand.
Fortunately, DocuSign provides SDKs for different languages that make it easier to communicate with the Authorization Server and REST APIs.
There’s a library for Java which we will use in the Groovy script.
Finally, let’s have a look at a diagram which illustrates the scenario covered in this blog post:
We can see that iFlow itself is responsible of calling DocuSign for fetching a JWT token.
I’ve called the box “Apps and Keys” because that’s the dashboard for creating an OAuth client.
Afterwards, the iFlow will use the JWT token to call the REST APIs at DocuSign.
Finally the usual disclaimer:
The content of this blog post is in no way an official reference nor recommendation neither from CPI nor from Docusign.
It is just my personal learning which I like to share with the community.
1. DocuSign: OAuth Client
To authenticate against Docusign, the usage of OAuth 2.0 is required.
With other words: we need to fetch a JWT token, then use it for calling a REST endpoint.
A JWT token is a string which contains a JSON object, carrying logon information.
To fetch a JWT token, we need to fire an HTTP request to Docusign's “Authorization server”.
Thus, we need to know the URL of that auth server.
Furthermore, to fetch a JWT token, we need credentials for authenticating at the auth server.
With other words, we need an “OAuth client” which is registered at the auth server, and its credentials.
In auth-server-language, such OAuth client is also called “app”.
Reason:
Typically, a web app needs access to a protected resource on behalf of the user who accessed the app.
Thus, the app acts as a client and contacts the auth server, which provides access by sending an access-token (usually in JWT format).
In our scenario, the “protected resource” is the Docusign REST API.
After this intro, we go ahead and use the Docusign dashboard to create an OAuth client (app)
We login at https://admindemo.docusign.com
On the left navigation bar, we scroll down to “Integrations” and click on “Apps and Keys”.
Direct access: https://admindemo.docusign.com/apps-and-keys/
Before we create anything, we can already take a note of some required information:
In the section “My account information” we see the “User ID” which we copy and store on a scratchpad for later use.
Also, the “Account Base URI” will be required later.
We click on “Add App and Integration Key” and enter a name like e.g. “iFlow”:
We enter a name, e.g. "iFlow".
In the details page, we see the “Integration Key” in the section “General Info”
We use the “copy” button to take a note of the guid, which we will need later.
Finally, we should not forget to press “Save” at the bottom of the page.
Summary:
The information we’ve stored for later use:
2. DocuSign: Consent
What is it about?
We’ve already described the OAuth flow above:
A web app needs to call some remote service to obtain some data of the current end-user.
Hence, the user is prompted by the auth server to enter credentials, then the auth server issues a JWT token.
The web app can use that JWT token to call the remote service endpoint (which is protected with OAuth and with that auth server).
Now we can imagine that the remote service is powerful, hosts log of information about the user.
As such, the remote service distinguishes different “scopes” for a service a´call.
For instance, calling the service for accessing the resource "photos" but with limited scope of only "viewing".
Other scopes can be adding or changing data, executing actions, etc
Scopes of "viewing" or "changing" are usually tied to personas like "Admins" or "Users".
When the web app fetches a JWT token, it may request specific scopes.
The auth server will then ask the user, if he agrees that the web app e.g. accesses the photo resources and e.g. applies a stamp on them.
Then the user not only has to enter credentials, but also confirm his “consent” to the usage of those “scopes” or “authorizations”.
That's it about "consent".
In our scenario, we now have created an OAuth client which enables us to fetch a JWT token.
However, the procedure will fail if the user hasn’t confirmed his consent beforehand.
It fails with a helpful error message:
Error while requesting server, received a non successful HTTP code 400 with response Body: '{"error":"consent_required"}'
OK
Now that we’ve understood the requirement, we can act upon it.
Before the flow is executed, we ask the user for consent.
To do so, we need to compose a URL which is sent to the user.
To learn how to do it, we read the documentation
here.
Or we continue reading this blog post….
The pattern of a consent-URL is as follows:
<authUrl>?response_type=<type>&
scope=<scopes>&
client_id=<id>&
redirect_uri=<uri>
Description
🔸authURL
The base url for authentication has to be looked up in the
documentation.
We see these 2 options:
https://account-d.docusign.com for the development environment
https://account.docusign.com for all production environments
In our example, we use the first one, as we’re using a the free developer test account.
In addition, we need to append the path segments for authentication endpoint, looked up in the same documentation page.
In our example we append
oauth/auth
This is the endpoint used for the OAuth flow “Authorization Code”, which is also used for the user consent
🔸response_type
This indicates the OAuth flow that is followed.
In case of “Authorization Code” flow, the auth server responds to the first request with a “code” instead of a token.
The code is sent to the redirect URI.
🔸scope
A list of scopes, separated by a <space>
A scope represents a permission or authorization, which is required to access specific data.
In our example we need the scopes
signature and
impersonation.
🔸client_id
As OAuth flows are initialized by applications resp. OAuth clients, we need to pass the ID of the OAuth client.
We copy the ID from the scratchpad where we took a note.
Alternatively, we copy it from the Docusign
dashboard at “Apps and Keys”
There, the client id is called “Integration Key”.
🔸redirect_uri
As mentioned before, the OAuth flow “Authorization Code” doesn’t respond to the initial request with a JWT token, but instead it sends a code to the redirect uri.
This adds another level of security, because the redirect uri has to be configured at the auth server.
This is done during the OAuth Client creation in the Docusign dashboard at “Apps and Keys”.
In our example, we only want to obtain user consent, so we don’t need any “code” nor token at this point.
But it is required, because of the design of the flow.
OK.
Finally, we’re able to compose the consent URL
In my example it looks as follows:
https://account-d.docusign.com/oauth/auth?response_type=code&scope=impersonation%20signature&client_...
Note:
the %20 is a URL escape character and represents a space
In your case it should be pretty much the same, except the client id.
We will invoke that URL later, when we run our scenario.
For now, we add the URL to our scratchpad.
3. DocuSign: Libraries
As we know, DocuSign provides REST APIs, means service endpoints, means URLs that can be invoked with any tool or from any program to programmatically execute functionality.
That's a great help already.
But DocuSign not only provides a REST API, it also provides libraries to enable the usage.
See the
SDKs home page.
In our scenario, we need the Java SDK for electronic signature:
docusign-esign-java
More precisely, we want to use the library from our Groovy script in the iFlow.
As such, we will need to upload it to the iFlow.
Download
But first of all, we have to get it onto our local machine.
Two notes here:
Note 1: the libraries
We not only need the Docusign library, but also all of its dependencies
Note 2: the version
We cannot just use the newest version because it must match the runtime in CPI.
I found that the version 3.23.0 is suitable (might change in future).
Note 3: the libraries (again)
Note that all the dependencies of all dependencies must have a matching version as well.
And one more note...
Note 4: the download
I see 2 options to get all the required libraries
- use maven to traverse the dependency tree and download everything in proper version
We go for this option below
- manually check all libs and their dependencies and download from maven central repo
Description for non-maven users can be found in the
appendix 3
Download dependencies using maven
We can proceed as follows, to download the required libraries with maven:
Create folder with dummy name
Create
c:\dummyfolder\pom.xml file with content copied from
appendix 2 .
🔷 pom.xml
<project ...>
<packaging>jar</packaging>
. . .
<dependency>
<groupId>com.docusign</groupId>
<artifactId>docusign-esign-java</artifactId>
<version>3.23.0</version>
<classifier>shaded</classifier>
With this descriptor, we tell maven to download the docusign lib and all of its dependencies into one “shaded” jar file (in our local maven repo).
The “test” project itself is just a dummy and can be deleted after download.
To trigger the download, we build the test project as follows:
We jump into the dummy project folder and run
mvn package
(or any similar command of your choice)
First, all dependencies are downloaded into the
.m2/repository folder
Then the test project is built
After build, we go to the local maven repo, in my case it is located at
c:\users\joe.m2/repository
Then we search for our desired docusign jar at this path:
C:\Users\joe\.m2\repository\com\docusign\docusign-esign-java\3.23.0\docusign-esign-java-3.23.0-shaded.jar
We can see that it has a big size and it contains everything needed, flattened into one jar.
That's great and suitable for our intentions.
4. CPI: Private Key
In the previous step we downloaded the RSA private key from DocuSign.
We need to make it available to the iFlow which we’re going to create below.
As we know, a private key is so sensitive and so powerful like a good old password.
As such, we upload it to a secure store in CPI, which is the
Keystore.
However…
The CPI Keystore does not allow to upload a single key fie.
It requires a bundle, or at least a key pair, with certificate.
So we would need to use openssl or a tool to generate a certificate and put it into a container file, before uploading.
Fortunately, there’s a feature in CPI that allows us to upload a private key and generates a corresponding certificate on the fly.
We only need to provide some info for the certificate, but we can enter dummy data, because we won't need the certificate.
So we go to
Monitor – Integrations – Manage Security – Keystore
Then we press
Add -> RSA Key and fill out the mandatory fields of the dialog:
🔸Alias
First we enter an alias name which we have to remember because we will use in the code.
In my example: "docusignkey"
🔸File
We browse to the "private.key" file which we downloaded from the DocuSign portal.
The path is stored on our scratchpad.
🔸Country
It is a required field but not relevant for us, so enter just anything.
That’s it.
5. CPI: iFlow
We create an iFlow that has the sole purpose to demo how to programmatically fetch a JWT token that can be used to call the Docusign REST API.
The groovy script can be easily embedded into any custom scenario that needs to call DocuSign API.
Let’s go through the steps
🔷Timer
🔸 We define a start event via Timer with default properties, i.e. run once
🔷Groovy Script
The script performs the following steps:
- Fetch the private key
- Convert the key object
- Fetch JWT token
- Use the token
So let’s see the code.
1. Fetch key
Previously, we’ve uploaded the private key, which we got from DocuSign, to the CPI keystore.
So now we can access it from the groovy script:
KeystoreService keystoreService = ITApiFactory.getService(KeystoreService.class, null)
KeyPair keyPair = keystoreService.getKeyPair("docusignkey")
PrivateKey privateKey = keyPair.getPrivate()
Note:
You might need to adapt the name of the alias of the key pair
However, the returned format is not suitable for the
Bouncycastle library, which is internally used by the DocuSign library.
As such, we have to convert it.
2 Convert
What we get from the keystore is the key in binary
DER format, but the expected format is
PEM.
So we have to convert now.
To avoid uploading another library to CPI, we just do the conversion manually:
- We Base64-encode the bytes of the private key.
- And we surround the string with the header and footer, as defined by the PEM format.
String pemString = "-----BEGIN PRIVATE KEY-----\n"
pemString = pemString + Base64.getEncoder().encodeToString(privateKey.getEncoded()) + "\n"
pemString = pemString + "-----END PRIVATE KEY-----\n"
byte[] privateKeyBytes = pemString.getBytes()
Note:
Why we use
BEGIN PRIVATE KEY instead of
BEGIN RSA PRIVATE KEY ?
First let’s clarify:
BEGIN RSA PRIVATE KEY
This is defined in PKCS #1 and supports only RSA.
BEGIN PRIVATE KEY
Defined in PKCS #8 and is more generic, contains header with information about the algorithm.
(
security glossary blog post)
When downloaded from Docusign, the private key was formatted according to PKCS #1
Seems that during import, the more generic format PKCS #8 is applied.
This can be confirmed by checking the result of
privateKey.getFormat()
In the code.
Anyways, both formats work fine for our scenario and are supported by the internal Bouncycastle lib
3. Fetch JWT token
Next step: now we can use the private key bytes to fetch the JWT token, using the DocuSign client library:
ApiClient apiClient = new ApiClient(https://demo.docusign.net/restapi);
apiClient.setOAuthBasePath("account-d.docusign.com");
ArrayList<String> scopes = new ArrayList<String>();
scopes.add("signature");
scopes.add("impersonation");
try{
OAuthToken oAuthToken = apiClient.requestJWTUserToken(
"0788b811-e4a4-4e3d-9d2f",
"a38a918a-74a2-4c94-ac85",
scopes,
privateKeyBytes,
3600);
String accessToken = oAuthToken.getAccessToken();
Explanation:
🔸apiClient.requestJWTUserToken
This is the method for using the JWT Grant type, which we use instead of the “Authorization Code Grant”.
The Javadoc can be found
here.
🔸OAuthBasePath
The Base URL which I’m using in this POC is the developer test account which I’m using and which is free.
For productive usage: account.docusign.com
The Info can be found in the
documentation.
🔸scopes
The required scopes depend on the use case and have to be copied from the Docusign documentation.
The list of scopes can be found
here.
signature: This scope is obviously required in scenarios with DocuSign electronic signature involvement.
impersonation: We need this scope because in our scenario, we don’t have a user-centric web application.
We call the API in a service-to-service manner, with a guid that mimics (impersonates) a real user.
So we have to use the user id. So we need impersonation.
🔸First GUID
This is the client id, which has to be copied from the OAuth client in the Docusign portal
It is also called “Integration Key” in the Docusign dashboard.
🔸Second GUID
This is the user ID, which is the guid of the user which will be impersonated.
We can see it in the Docusign dashboard, at “Apps and Keys” on the top section “My Account Information”
The User ID is equal for all OAuth clients
🔸privateKeyBytes
The pem-formatted private key, as byte array from the pem string.
🔸3600
The value for “expiresIn”, the number of seconds remaining before the JWT assertion is considered as invalid.
Note:
if the provided private key is not the right one, which is matching the App details of docusign, the error indicates it properly:
APIException: Error while requesting server, received a non successful HTTP code 400 with response Body: "error":"invalid_grant","error_description":"no_valid_keys_or_signatures"
4. Use the token
Once we have received the token, we write it to the log, to check it later
We can also do a little test to see if the token is usable.
A simple API call would be to request the user info and print some info to the log
messageLog.addAttachmentAsString("JWT token", "The JWT token: " + token, "text/plain");
UserInfo user = apiClient.getUserInfo(token);
messageLog.addAttachmentAsString("Info", "User: " + user.getName() + " with mail: " + user.getEmail(), "text/plain")
5. What's next?
In a productive scenario, you would proceed with scripting, for calling the REST endpoint.
Or you let CPI do the REST calls with a Request/Reply step.
In any case, you have to set the access token as header:
message.setHeader("Authorization", “bearer ” + token)
Note the blank after "bearer ".
6. Exception handling
Fetching a JWT token is only possible if the user has provided his consent to the operation.
This has to be done beforehand.
The usual scenario is a webapp where the user logs into an app and is asked if it is OK to provide permission to the app to access e.g. the user’s cat photos.
Thus in an user-centric interactive scenario the user does the consent-click personally.
In our iFlow scenario we don’t have the chance to get the user’s consent interactively.
The workaround offered by CPI is the “Authorization Code” Security artifact, which requires interactive user logon one time, then CPI uses the refresh token to get a new JWT.
However, as we've learned, this workaround is not possible with DocuSign Authorization server.
That’s why we’re implementing the JWT grant, for non-user-centric scenario.
But the user consent is required anyways, only one time.
So the user has to be informed beforehand, that he has to do the consent-click before running the iFlow.
The docusign client throws an exception if consent is missing, so we can provide a detailed error message
catch (ApiException exp){
if (exp.getMessage().contains("consent_required")){
messageLog.addAttachmentAsString("Error_Consent", "Consent required. Has to be provided in browser. Then run iFlow.", "text/plain");
That’s it for the iFlow.
We save it, but don't deploy yet.
6. CPI: Libraries
The script won’t compile without finding the dependencies at runtime.
As such, we need to upload the required jar file, which we downloaded
before.
We click in the background of the iFlow designer, bring up the property sheet and open the tab “References”.
We click on
Add -> Archive and browse to the jar file which we downloaded before.
Remember?
The path was something like
C:\Users\joe\.m2\repository\com\docusign\docusign-esign-java\3.23.0\docusign-esign-java-3.23.0-shaded.jar
7. Run Scenario
At this point we’re ready with all preparation steps.
- prepared Docusign authentication (OAuth client)
- uploaded key to CPI
- uploaded libraries to iFlow
- created script
- upps 😵💫....
We almost forgot.....one step is missing:
Before we run the iFlow, we have to provide the user consent.
Above, we’ve prepared the URL, which is tied to our OAuth client.
We can now take over the role of the user and open the URL:
https://account-d.docusign.com/oauth/auth?response_type=code&scope=impersonation%20signature&client_...
Note:
You have to adapt the value of the client id
In the login screen, we enter the user-mail and password of our demo-account-user
Afterwards, we see the famous consent-screen:
The first required permission, “…without you being present” is the human readable description of the “impersonation” scope.
The second one represents the “signature” scope.
After allowing the access, we can see that the browser is calling the URL which we configured as redirect, and in the URL-field of the browser we can see that the “code” is been sent...
But we ignore it...
🙈
Last step
So now we can deploy our iFlow.
After deploy, it is started automatically by the timer
But we’re not happy until we….
Post last step
… until we check the result in the CPI log.
The iFlow is not only executed successfully (as we catch the possible exceptions, the iFlow will always be completed and green) but the log contains the user Info which we’re writing in the log.
Summary
In this blog post we’ve learned how to call a Docusign API from an iFlow:
- Docusign REST APIs are protected with OAuth 2.0.
- Docusign supports “Authorization Code” flow.
- CPI supports this flow as well, but relies on refresh tokens (non-user-interactive).
- Docusign implements the refresh token behavior in a way that makes it unusable for CPI.
- Alternatively, we use the JWT-Grant, supported by Docusign, but need to implement in Groovy script.
- In Docusign dashboard, we create an OAuth client and download private key.
- In CPI we upload the private key.
- Docusign provides a Java SDK which we use in Groovy script to fetch a JWT token.
- We download the Docusign-jar and upload in CPI.
- We need to consider the possible version and all dependencies.
- Other challenge to consider: the consent has to be granted by user before calling the Docusign endpoint.
This tutorial has been focusing on obtaining a JWT token to call the REST APIs at Docusign.
Actually calling an API for e.g. requesting remote signing by a recipient, has not been considered.
Key Takeaways
The suitable authentication type used in this tutorial:
JWT Grant
Create OAuth client with no secret, but
RSA key
Upload key in CPI via
Add RSA
Use Docusign
SDK to fetch JWT token and call API
Currently, only
version 3.23.0 of docusign java eSignature can be used in CPI
Use maven to download jars
User
consent has to be granted once, beforehand
Links
Docusign base URLs
demo:
account-d.docusign.com
staging:
account-s.docusign.com
productive:
account.docusign.com
Docusign Dashboard
Account login:
https://admindemo.docusign.com/apps-and-keys/
DocuSign documentation
Authentication
reference
Choose OAuth type
Obtain and revoke
consent
How to set
redirect uri
List of
scopes
REST APIs
main page
REST API for
eSignature
SDK for eSignature in Java
The
Javadoc (need to adjust the version)
DocuSign Blog
Important info about
JWt Grant and consent
SAP Help Portal:
CPI
Scripting APIs
Specifications
OAuth 2.0
Understanding of
OAuth for dummies like me.
OAuth 2.0 spec
Other
Security Glossary Blog.
The Appendix: Sample Code
Note that you need to adapt the guids and URLs.
Appendix 1: The Groovy Script
🔷 groovy script
import com.sap.gateway.ip.core.customdev.util.Message;
import com.sap.it.api.ITApiFactory;
import com.sap.it.api.keystore.KeystoreService;
import java.security.KeyPair;
import java.security.PrivateKey;
import com.docusign.esign.client.ApiClient;
import com.docusign.esign.client.ApiException;
import com.docusign.esign.client.auth.OAuth.OAuthToken;
import com.docusign.esign.client.auth.OAuth.UserInfo;
import com.docusign.esign.client.auth.OAuth.Account;
def Message processData(Message message) {
def messageLog = messageLogFactory.getMessageLog(message);
// fetch the public key from CPI Keystore
KeystoreService keystoreService = ITApiFactory.getService(KeystoreService.class, null)
KeyPair keyPair = keystoreService.getKeyPair("docusignkey");
PrivateKey privateKey = keyPair.getPrivate()
// convert to PEM
String encodedString = "-----BEGIN PRIVATE KEY-----\n";
encodedString = encodedString+Base64.getEncoder().encodeToString(privateKey.getEncoded())+"\n";
encodedString = encodedString+"-----END PRIVATE KEY-----\n";
byte[] privateKeyBytes = encodedString.getBytes();
// fetch JWT token
ApiClient apiClient = new ApiClient(https://demo.docusign.net/restapi);
apiClient.setOAuthBasePath("account-d.docusign.com");
ArrayList<String> scopes = new ArrayList<String>();
scopes.add("signature");
scopes.add("impersonation");
try{
OAuthToken oAuthToken = apiClient.requestJWTUserToken(
"0788b811-e4a4-4e3d-9d2f-108d545e93df",
"a38a918a-74a2-4c94-ac85-4dfc069fac8f",
scopes,
privateKeyBytes,
3600);
String token = oAuthToken.getAccessToken();
// RESULT
messageLog.addAttachmentAsString("JWT TOKEN", "The JWT token: " + token, "text/plain");
// use the JWT token
UserInfo user = apiClient.getUserInfo(token);
messageLog.addAttachmentAsString("Info", "User: " + user.getName() + " with mail: " + user.getEmail(), "text/plain") // // == full name
}
catch (ApiException exp){
if (exp.getMessage().contains("consent_required")){
messageLog.addAttachmentAsString("Error_Consent", "Consent required, please provide consent in browser window and then run this app again.", "text/plain");
}else{
messageLog.addAttachmentAsString("APIException", "APIException: " + exp.getMessage(), "text/plain");
}
}catch (Exception e){
messageLog.addAttachmentAsString("Exception", "Exception: " + e.getMessage(), "text/plain");
}
return message;
}
Appendix 2: Maven Project Dependency
🔷 pom.xml
<project xmlns=http://maven.apache.org/POM/4.0.0 xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xsi:schemaLocation=http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd>;
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>1</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.docusign</groupId>
<artifactId>docusign-esign-java</artifactId>
<version>3.23.0</version>
<classifier>shaded</classifier>
</dependency>
</dependencies>
</project>
Appendix 3: Manually configure Dependencies
We need the following 14 jar files with these concrete versions.
Note that the versions can be increased, once the java runtime in CPI is increased.
The files have to be uploaded individually, but multi-selection is supported.
For your convenience, I’m providing direct link for download of each required jar file.
(find the files from here:
https://mvnrepository.com)
DocuSign eSignature Java
docusign-esign-java-3.23.0.jar
https://repo1.maven.org/maven2/com/docusign/docusign-esign-java/3.23.0/docusign-esign-java-3.23.0.ja...
Bouncy Castle Provider
bcprov-jdk15on-1.69.jar
https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.69/bcprov-jdk15on-1.69.jar
Apache Oltu OAuth 2.0 Client
org.apache.oltu.oauth2.client-1.0.2.jar
https://repo1.maven.org/maven2/org/apache/oltu/oauth2/org.apache.oltu.oauth2.client/1.0.2/org.apache...
Auth0 Java Jwt
java-jwt-3.4.1.jar
https://repo1.maven.org/maven2/com/auth0/java-jwt/3.4.1/java-jwt-3.4.1.jar
GlassFish Jersey Core Client
jersey-client-2.29.1.jar
https://repo1.maven.org/maven2/org/glassfish/jersey/core/jersey-client/2.29.1/jersey-client-2.29.1.j...
GlassFish Jersey Core Common
jersey-common-2.29.1.jar
https://repo1.maven.org/maven2/org/glassfish/jersey/core/jersey-common/2.29.1/jersey-common-2.29.1.j...
GlassFish Jersey Ext Entity Filtering
jersey-entity-filtering-2.29.1.jar
https://repo1.maven.org/maven2/org/glassfish/jersey/ext/jersey-entity-filtering/2.29.1/jersey-entity...
GlassFish Jersey Inject HK2
jersey-hk2-2.26.jar
https://repo1.maven.org/maven2/org/glassfish/jersey/inject/jersey-hk2/2.26/jersey-hk2-2.26.jar
GlassFish Jersey Media JSON Jackson
jersey-media-json-jackson-2.29.1.jar
https://repo1.maven.org/maven2/org/glassfish/jersey/media/jersey-media-json-jackson/2.29.1/jersey-me...
GlassFish Jersey Media Multipart
jersey-media-multipart-2.29.1.jar
https://repo1.maven.org/maven2/org/glassfish/jersey/media/jersey-media-multipart/2.29.1/jersey-media...
GlassFish HK2 API Module
hk2-api-2.5.0-b42.jar
https://repo1.maven.org/maven2/org/glassfish/hk2/hk2-api/2.5.0-b42/hk2-api-2.5.0-b42.jar
GlassFish HK2 Implementation Utilities
hk2-utils-2.5.0-b42.jar
https://repo1.maven.org/maven2/org/glassfish/hk2/hk2-utils/2.5.0-b42/hk2-utils-2.5.0-b42.jar
GlassFish HK2 ServiceLocator Default Implementation
hk2-locator-2.5.0-b42.jar
https://repo1.maven.org/maven2/org/glassfish/hk2/hk2-locator/2.5.0-b42/hk2-locator-2.5.0-b42.jar
FasterXML Jackson Core
jackson-core-2.12.1.jar
https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.12.1/jackson-core-2.12.1.ja...