How to use JWT with Salesforce API?

This article will be specifically about getting JWT ( Javascript Web Token ) to work with Salesforce API using Java. Parts of these you can use for other things, but the logic is pretty much the same.

Here are the major steps:

  1. Generate a Certificate, key and keystore.
  2. Upload to Salesforce
  3. Generate a JWT
  4. Send it through API to Salesforce

How to generate certificate, key and keystore?

  1. Download the useful tool ( http://keystore-explorer.org/ ). This tool pretty much is a nice UI on top of keytool and jarsigner. You can do it all through command prompt, but this is actually pretty nice tool.
  2. Read the instructions from the link ( https://support.code42.com/Administrator/5/Configuring/Create_a_signed_keystore_with_the_KeyStore_Explorer#Build_the_keystore )
  3. I only did steps 1 and 2. Steps 3 is useful when you are doing production signed stuff. But for me, I was getting a POC (proof of concept) working first.

Upload Certs to Salesforce

  1. Follow the instructions from Salesforce: https://developer.salesforce.com/docs/atlas.en-us.salesforce_developer_environment_tipsheet.meta/salesforce_developer_environment_tipsheet/salesforce_developer_environment_remoteaccess.htm
  2. Note and save the Secret Key and consumer key. You will need this later on in the coding.

Libraries need to include in your java project

Here are the libraries that I included in my maven project (or you can download the jar yourself). I’ll have code that reference to these in the example


Code the JWT Generation

Keep in mind these examples aren’t final code. It is just to give you an idea. You can refactored all the examples below as a util and make your life a lot easier.

public static String generateJwt() {
try {
String jksKeyStoreFilePath = ""; // Path to the *.jks file
String keyStorePassword = ""; // The password that the keystore
// is locked with
    KeyStore keystore = KeyStore.getInstance("JKS")
keystore.load(new FileInputStream(jksKeyStoreFilePath),
keyStorePassword.toCharArray());
PrivateKey privateKey = (PrivateKey)
keystore.getKey(keyStoreAlias,
keyStorePassword.toCharArray());
    String iss = ""; // consumer ID from Salesforce
String sub = ""; // username that you are logging in as
String aud = ""; // https://test.salesforce.com or
// https://login.salesforce.com
String jwt = Jwts.builder() //
.setIssuer(iss) // identifies principal that issued the JWT
.setSubject(sub) // identifies the subject of the JWT
.setExpiration(exp) // identifies the expiration time on or
// after which the JWT must not be
// accepted for processing
.setAudience(aud) // identifies the recipients that the
// JWT is intended for
.signWith(SignatureAlgorithm.RS256, privateKey) //
.compact();
return jwt;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

Obviously you want to replace some of the variables with what they should be. The returned value is the JWT that you would be using in a later code example to get the next steps.


Code the Salesforce API call with JWT to get Access Token

Pass in the generateJwt() as a perimeter to this method.

private static String generateAccessToken(String jwt) {
  String tokenEndpoint = ""; // https://test.salesforce.com or 
// https://login.salesforce.com
String consumerID = ""; // From Salesforce
String consumerSecret = ""; // From Salesforce
  HttpClient httpclient = new HttpClient();
PostMethod post = new PostMethod(tokenEndpoint);
post.addParameter("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
post.addParameter("assertion", jwt);
post.addParameter("client_id", consumerId);
post.addParameter("client_secret", consumerSecret);
post.addParameter("redirect_uri", "https://localhost:1717/OauthRedirect");
String responseBody = null;
try {
httpclient.executeMethod(post);
responseBody = post.getResponseBodyAsString();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
String accessToken = null;
JSONObject json = null;
try {
json = new JSONObject(responseBody);
Map<String, Object> map = json.toMap();
if (map.containsKey("error")) {
System.out.println(map.get("error"));
System.out.println(map.get("error_description"));
} else {
accessToken = json.getString("access_token");
return accessToken;
}
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}

This access token will be used to login and get access to the SalesforceConnection.

public static SoapConnection createSoapConnection(String accessToken) throws ConnectionException {
ConnectorConfig config = new ConnectorConfig();
config.setSessionId(accessToken);
config.setServiceEndpoint(“https://login.salesforce.com/services/Soap/c/41.0");
return config;

return new SoapConnection(config);
}

Understanding the different endpoints

What you see above is a hard coded example of creating a SOAP connection to Salesforce being logged in as the user that you passed in as the sub of the jwt. There is a whole way to secure and only allow certain users to log in this way. But you can also make it dynamic so that this is can execute as different users as if they were doing it themselves. Thus, why it is an API.

Instead of hardcoding the ServiceEndpoint as I did here, you can make a call to UserInfo endpoint to find out the actual url that is specific to your org and instance. You can find more information here at the docs: https://help.salesforce.com/articleView?id=remoteaccess_using_userinfo_endpoint.htm&type=5

Each end point will be access for different APIs that Salesforce provides. You can find out more either looking at the docs: https://developer.salesforce.com/page/Salesforce_APIs

Or take the Trailhead: https://trailhead.salesforce.com/en/modules/api_basics/units/api_basics_overview


***EDITED BELOW

Maven Snippet

There was a request to show my maven project. I can’t show the whole file since there is a bunch of work related items in there. But I can show the dependencies that are related to the snippets above.

<dependencies>
<dependency>
<groupId>com.force.api</groupId>
<artifactId>force-wsc</artifactId>
<version>42.0.0</version>
</dependency>
<dependency>
<groupId>com.force.api</groupId>
<artifactId>force-partner-api</artifactId>
<version>42.0.0</version>
</dependency>
<dependency>
<groupId>com.force.api</groupId>
<artifactId>force-metadata-api</artifactId>
<version>42.0.0</version>
</dependency>
<dependency>
<groupId>com.force.api</groupId>
<artifactId>force-apex-api</artifactId>
<version>42.0.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.9.1</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.58</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>