From the Trenches: Authenticate without user interaction via a system user
Discover how to configure your apps for authentication without the need for user interaction by using a system user account.
Automating business processes is becoming a need for every organization, and that’s why Docusign provides JSON Web Token (JWT) Grant authentication as one of the OAuth 2.0 authentication flows. Using JWT enables organizations to build system integrations with Docusign by using a system user, eliminating the need for user interaction to input credentials.
Possible scenarios
When using JWT Grant, you have two possible scenarios to impersonate a user to be able to call Docusign APIs:
Using a single system user, as I will explain in this blog post.
Using the “Send on Behalf Of” feature as explained in a previous blog post, From the Trenches: OAuth 2.0 and the new SOBO.
Prerequisites
To use JWT Grant authentication with a system user, you must meet the following prerequisites:
You have defined an integration key.
An integration key identifies your integration and links to its configuration values. To create an integration key:
Open the Apps and Keys page.
Under My Apps / Integration Keys, select Add App / Integration Key, then give it a name.
You have defined a redirect URI for your integration key.
The redirect URI is the URI (URL) to which Docusign will redirect the browser after authentication. In the case of JWT Grant, this is only used during the consent process, as I explain later on. To define a redirect URI:
Open the Apps and Keys page.
Under My Apps / Integration Keys, choose the integration key to use, then select Actions, then Edit.
In the Additional Settings section, select Add URI.
Enter the new redirect URI. This can be a localhost address.
Your application has an RSA key pair.
To add an RSA key pair to your application:
Open the Apps and Keys page.
Under My Apps / Integration Keys, choose the integration key to use, then select Actions, then Edit.
In the Authentication section, select Add RSA Keypair.
Save both the public and private keys to a secure place, including the
----BEGIN RSA PRIVATE KEY----
and----END RSA PRIVATE KEY-----
lines as part of the text.
Step 1: Request application consent
Before using the system user with the Docusign APIs, you need to grant consent for your integration to impersonate that system user. This will be a one-time manual step through your browser that you will not need to do again unless someone manually revokes that consent.
To grant consent, you need to construct a URI like the one below.
https://YOUR_AUTH_BASE_PATH/oauth/auth?
response_type=code
&scope=YOUR_REQUESTED_SCOPES
&client_id=YOUR_INTEGRATION_KEY
&redirect_uri=YOUR_REDIRECT_URI
Just replace the placeholders with their respective values:
YOUR_AUTH_BASE_PATH | For the eSignature REST API, use the following: For the developer environment, it should be account-d.docusign.com For the production environment, it should be account.docusign.com For other APIs, see Docusign API endpoint base paths. |
YOUR_REQUESTED_SCOPES | A space-delimited list of scopes your integration needs. The basic scopes needed for eSignature are signature and impersonation. For a full list of available scopes per Docusign API, see Authentication scopes. |
YOUR_INTEGRATION_KEY | Your integration key. |
YOUR_REDIRECT_URI | A URL-encoded redirect URI that matches one of the redirect URIs configured for the integration key being used. |
An example of the consent URI would look like this:
https://account-d.docusign.com/oauth/auth?
response_type=code
&scope=signature%20impersonation
&client_id=d3xxxx6f-exx5-4xxc-9xxb-f2fxxxxxx41a
&redirect_uri=https://www.example.com/
Next, pasting this link into your browser will direct you to a login page where you should input the credentials of the system user. After that a consent screen will come up like the screenshot below to prompt you to accept or reject the permission of the application to impersonate that user.
Step 2: Create the JWT assertion (not needed for Docusign SDKs)
To authenticate in the JWT Grant flow, you will need to create a JWT assertion containing data on the authentication request, then exchange it for an access token. If you’re using a Docusign SDK, the SDK performs this step for you.
A Docusign JWT contains three JSON blocks that are encoded (https://jwt.io/ can verify the encoding for you) and separated by period characters:
Header:
{
"alg": "RS256",
"typ": "JWT"
}
Payload:
{
"iss": "<your_integration_key>",
"sub": "<your_user_id>",
"aud": "account-d.docusign.com",
"iat": <current_unix_epoch_time>,
"exp": <current_unix_epoch_time>,
"scope": "signature impersonation"
}
"sub"
: the user ID of the user to be impersonated."iat"
: the start time of the validity of this JWT assertion."exp"
: the expiration time of the validity of this JWT assertion.For Unix epoch time for the
"iat"
and the"exp"
parameters, use https://www.epochconverter.com/.
Signature:
RSASHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(body),
"<your_public_rsa_key>",
"<your_private_rsa_key>"
)
Step 3: Generating an access token
Using Docusign’s SDKs:
No need to worry about the JWT assertion in step 2, as Docusign SDKs handle that in the background.
Generating an access token using the SDKs is really simple as shown below.
C#
var clientId = "d30dc66f-xxxx-xxxx-xxxx-f2ff5c4c441a"; //Integration Key
var impersonatedUserId = "b7119312-xxxx-xxxx-xxxx-044f57b1654f";
var authServer = "account-d.docusign.com";
var docusignClient = new DocuSignClient();
OAuthToken accessTokenResult = docusignClient.RequestJWTUserToken(
clientId,
impersonatedUserId,
authServer,
File.ReadAllBytes("private.key"), // "private.key" file containing the RSA private key
3600);
Console.WriteLine("JWT Access Token:");
Console.WriteLine(accessTokenResult.access_token);
Java
ApiClient apiClient = new ApiClient();
apiClient.setOAuthBasePath("account-d.docusign.com"); //Change to "account.docusign.com" for production
ArrayList<String> scopes = new ArrayList();
scopes.add("signature");
scopes.add("impersonation");
byte[] privateKeyBytes = Files.readAllBytes(Paths.get("private.key")); // "private.key" file containing the RSA private key
OAuthToken oAuthToken = apiClient.requestJWTUserToken(
"c31826b6-xxxx-xxxx-xxxx-9bbd3b3fbf40", //Integration Key
"e694aa11-xxxx-xxxx-xxxx-9c7e3de0e723", //Impersonated User Id
scopes,
privateKeyBytes,
3600);
String accessToken = oAuthToken.getAccessToken();
System.out.println("JWT Access Token: " + accessToken);
Node.js
const docusign = require('docusign-esign')
, fs = require('fs')
, oauthServer = "account-d.docusign.com"
, clientId = "c31826b6-xxxx-xxxx-xxxx-9bbd3b3fbf40" // Integration key
, impersonatedUserId = "e694aa11-xxxx-xxxx-xxxx-9c7e3de0e723"
, scopes = [ "signature", "impersonation"]
, jwtLifeSec = 60 * 60 // requested lifetime for the JWT
, dsApi = new docusign.ApiClient()
, rsaKey = fs.readFileSync("private.key"); // "private.key" file containing the RSA private key
async function GetTokenJWT() {
dsApi.setOAuthBasePath(oauthServer);
return results = await dsApi.requestJWTUserToken(
clientId,
impersonatedUserId,
scopes,
rsaKey,
jwtLifeSec
);
}
async function asyncMainline() {
await GetTokenJWT();
console.log(results.body.access_token);
}
asyncMainline()
PHP
<?php require 'vendor/autoload.php';
require 'vendor/docusign/esign-client/autoload.php';
require 'vendor/docusign/esign-client/src/Client/ApiClient.php';
require 'vendor/docusign/esign-client/src/Configuration.php';
require 'vendor/docusign/esign-client/src/Client/Auth/OAuth.php';
$config = (new Docusign\eSign\Configuration())->setHost("https://demo.docusign.net/restapi");
$oAuth = (new Docusign\eSign\Client\Auth\OAuth())->setOAuthBasePath("account-d.docusign.com");
$apiClient = new Docusign\eSign\Client\ApiClient($config, $oAuth);
$jwt_scope = array("signature", "impersonation");
$rsaPrivateKey = file_get_contents("private.key");
try {
$response = $apiClient->requestJWTUserToken(
"d30dc66f-xxxx-xxxx-xxxx-f2ff5c4c441a", // integration key
"e694aa11-xxxx-xxxx-xxxx-9c7e3de0e723", // impersonated user id
$rsaPrivateKey,
$jwt_scope
);
echo "Access Token:" . "<br><br>";
echo $response[0]['access_token'];
} catch (\Throwable $th) {
echo $th;
}
Python
def _get_private_key():
"""
Check that the private key present in the file and if it is, get it from the file.
In the opposite way get it from config variable.
"""
private_key_file = path.abspath(config["private_key_file"])
if path.isfile(private_key_file):
with open(private_key_file) as private_key_file:
private_key = private_key_file.read()
else:
private_key = config["private_key_file"]
return private_key
def _get_JWT_token(api_client):
private_key = _get_private_key().encode("ascii").decode("utf-8")
jwtToken = api_client.request_jwt_user_token(
client_id = config["ds_client_id"],
user_id = config["ds_impersonated_user_id"],
oauth_host_name = config["authorization_server"],
private_key_bytes = private_key,
expires_in = 3600,
scopes = ["signature", "impersonation"]
)
return jwtToken.access_token
Ruby
def authenticate
configuration = DocuSign_eSign::Configuration.new
configuration.debugging = true
api_client = DocuSign_eSign::ApiClient.new(configuration)
api_client.set_oauth_base_path(CONFIG['authorization_server'])
rsa_pk = 'docusign_private_key.txt'
begin
token = api_client.request_jwt_user_token(CONFIG['jwt_integration_key'], CONFIG['impersonated_user_guid'], rsa_pk, 3600, $SCOPES)
rescue OpenSSL::PKey::RSAError => e
Rails.logger.error e.inspect
raise "Please add your private RSA key to: #{rsa_pk}" if File.read(rsa_pk).starts_with? '{RSA_PRIVATE_KEY}'
raise
rescue DocuSign_eSign::ApiError => e
body = JSON.parse(e.response_body)
puts 'API Error'
puts body['error']
puts body['message']
exit
end
end
end
Using Postman:
Method: POST
URL (demo): https://account-d.docusign.com/oauth/token
URL (production): https://account.docusign.com/oauth/token
Params:
Key | Value |
---|---|
|
|
| The JWT assertion created in step 2 |
Step 4: Using the access token
The access token generated in step 3 is valid for one hour, and during this hour it can be used to call Docusign APIs by adding it as a bearer token in the authorization header.
Additional resources
Ahmed Shorim has been with Docusign since 2021 as a Senior Developer Support Advisory Engineer. His work is focused on advising developers on how to integrate with Docusign APIs and SDKs by consulting on best practices and providing code examples. Experienced in developing web, mobile, and desktop applications along with building automation flows, he can be reached at LinkedIn.
Related posts