JWT Bearer token exchange

The next step is to make an HTTP POST callout with the body assertion obtained in the previous section. The following is the code for the same (you can view the entire code at https://github.com/PacktPublishing/Learning-Salesforce-Einstein/blob/master/Chapter6/SalesforceEinsteinVision/src/classes/EinsteinVisionJWTBearerTokenExchange.cls):

     public static String getAccessToken(String accountId, 
Integer expirationTime, String privateKeyString ) {
String tokenendPoint =
'https://api.metamind.io/v1/oauth2/token';
String access_token = null;
String body =
'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-
bearer&assertion='+ EinsteinVisionJWT.issue
(accountId,expirationTime,privateKeyString);
HttpRequest req = new HttpRequest();
req.setMethod('POST');
req.setEndpoint(tokenendPoint);
req.setHeader('Content-type', 'application/x-www-form-
urlencoded');
req.setBody(body);
Http http = new Http();
HTTPResponse res = http.send(req);

if ( res.getStatusCode() == 200 ) {
System.JSONParser parser =
System.JSON.createParser(res.getBody());
while (parser.nextToken() != null) {
if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) &&
(parser.getText() == 'access_token')) {
parser.nextToken();
access_token = parser.getText();
break;
}
}
}
else{
system.debug('EXCEPTION CODE..'+res.getStatusCode());
system.debug('RESPONSE_BODY..'+res.getBody());
throw new EinsteinVisionAPIException(res.getBody());
}
return access_token;
}

Let's test the code so far using the developer console. The developer console is very handy to test Apex code snippets on Force.com. One can access the Salesforce developer console by clicking on their name and then clicking on the developer console, as shown in the following screenshot:

Paste the following code block in the developer console to test if the access token is properly generated:

ContentVersion base64Content = [SELECT Title, VersionData FROM ContentVersion where Title='Einstein_platform' ORDER BY Title LIMIT 1];
String privateKeyString = base64Content.VersionData.tostring();
privateKeyString = privateKeyString.replace('-----BEGIN RSA PRIVATE KEY-----', '');
privateKeyString = privateKeyString.replace('-----END RSA PRIVATE KEY-----', '');
privateKeyString = privateKeyString.replace(' ', '');
system.debug('ACCESS_TOKEN..'+EinsteinVisionJWTBearerTokenExchange.getAccessToken('[email protected]', 3600, privateKeyString));

Note that it is assumed that the private key file uploaded is named Einstein_platform. You will get an exception if you use any other name, and if your private key is mismatched. The exception in the case of an incorrect private key will be JWT is Invalid. The following screenshot shows the code to be executed in the developer console to test whether the service is returning a proper access token:

Once you execute the code snippet in the developer console, you will see an access token in the logs, as shown in the following screenshot:

Now that we have a nice utility to obtain the access token, we are all set to explore how to use platform features, such as custom settings, to store and manage the access token.

If you noticed, so far, the time span for the expiration of the access token is not infinite, and, for security reasons, it is advisable to keep the expiration time limited. The range can be between 15 minutes to 8 hours.

Hence, there is a need for an Apex utility that can store the access token and, in case the access token is expired and the application tries to make an API call to the Einstein Vision API, the error is gracefully handled and an API callout is invoked to get the new access token.

For a managed package application, it is always best practice to use protected custom settings to store the access token so that only the Apex script can access the access token. Managed package applications are applications available as a package, and to use it, a customer installs the package via the install wizard. The code of the managed package apps is completely protected and the app vendor owns the source code intellectual property (IP).

The entire process of keeping the access token current can be summarized with the help of the following flowchart:

Let's create a custom setting to store some of the configuration parameters related to the API and also create fields to store the access token and other parameters that are application specific and can change from one Salesforce account to another.

To create a custom setting, navigate to Setup | Develop | Custom Settings.

The following screenshot shows the custom setting. Note that we are making it protected so that, for managed package applications, only Apex can access them. If one needs to make a few fields configurable, a Visualforce or Lightning Component is recommended to allow the admins to configure:

At a minimum, we will need the following fields in the custom setting. This also complements the Apex class we wrote in the preceding Setting authorization between Salesforce and Einstein Vision APIs section:

Field Label Field Data Type Description
Access Token Text(255) This is required to store the access token.
AccountId Text(100) (Unique Case Insensitive) This is the ID of the Einstein Vision account. Usually, this is the email ID used to sign into an Einstein Vision account.
PrivateKeyName Text(100) This is the name of the File where the private key obtained during Einstein Vision is stored.
ExpirationTime Number(8, 0) This is the number of seconds in which the token will expire from the time it was generated.

The following is a screenshot of metadata for the custom settings:

Also, let's create the Default record with AccountId and the PrivateKeyName.

In order to manage the access token, we have created a utility class named EinsteinVisionAccessTokenManager. The Utility class can be viewed at https://github.com/PacktPublishing/Learning-Salesforce-Einstein/blob/master/Chapter6/SalesforceEinsteinVision/src/classes/EinsteinVisionAccessTokenManager.cls.

The Apex class uses an inner class that has a Boolean property, isChange, to indicate if accessToken needs to be updated.

Whenever there is a need for accessToken, the application will call the Apex method, getcurrentAccessToken. Consider the following code:

     public static AccessToken getcurrentAccessToken(){
EinsteinVision__c accessTokenRec =
EinsteinVision__c.getOrgDefaults();
if(accessTokenRec.AccessToken__c == null){
//Requires a call to the Einstein Vision Oauth Endpoint
return getNewToken(accessTokenRec);
}
else{
AccessToken currentoken =
new AccessToken(accessTokenRec.AccessToken__c,false);
return currentoken;
}
}

This method looks for the custom setting, and if the value of accessToken is null, an HTTP request is initiated to retrieve the new token and a flag that custom setting needs to update is set.

One of the errors commonly encountered in Apex is YOU HAVE UNCOMMITTED WORK PENDING. The cause of the error is described at https://help.salesforce.com/articleView?id=000079772&language=en_US&type=1. It's important that our EinsteinVisionAccessTokenManager class is designed considering this limitation. We do not update the custom settings before an Apex callout is made, as that will trigger the pending DML exception and instead allow the code to go through other callouts, such as training of data/predict/creation of dataset, and only after all the callouts are executed, we update the token. The update of the access token can be handled using the updateAccessToken method provided by the EinsteinVisionAccessTokenManager class.

The getNewToken method returns accessToken by calling the oauth endpoint. The method code is as follows:

    public static AccessToken getNewToken
(EinsteinVision__c accessTokenRec){
String token =
EinsteinVisionJWTBearerTokenExchange.getAccessToken
(accessTokenRec.AccountId__c,integer.valueof
(accessTokenRec.ExpirationTime__c),
getPrivateKey(accessTokenRec.PrivateKeyName__c));
AccessToken currentoken = new AccessToken(token,true);
return currentoken;
}

The getPrivateKey method is a private method that encloses all the code to get the string from the file that has the private key. Consider the following code:

    private static String getPrivateKey(String fileName){
String privateKeyString = '';
clist<ContentVersion> base64Content =
[SELECT Title, VersionData FROM ContentVersion where
Title=:fileName ORDER BY Title LIMIT 1];
if(base64Content.size()>0){
privateKeyString = base64Content[0].VersionData.tostring();
privateKeyString = privateKeyString.replace
('-----BEGIN RSA PRIVATE KEY-----', '');
privateKeyString = privateKeyString.replace
('-----END RSA PRIVATE KEY-----', '');
privateKeyString = privateKeyString.replace(' ', '');
}
else{
throw new AccessTokenManagerException
('The File Name for the Private Key is
wrongly configured .Contact Your Admin');
}
return privateKeyString;
}

The class also provides a public updateAccessToken method. This method allows us to update the access token if the current token is expired. Use this method only after all the callouts have been executed. In case of a callout failure, use the catch block and make sure to update the token.

The following screenshot shows how one can use EinsteinVisionAccessTokenManager to get accessToken:

When the transaction request is completed, one can check for the ischanged flag and call the method to update the token. The following lines of code give us an idea of how one can make use of the utility:

    EinsteinVisionAccessTokenManager.AccessToken accessTokenObject = 
EinsteinVisionAccessTokenManager.getcurrentAccessToken();
...
//All other callouts
//Other processing
....
if(accessTokenObject.isChanged){
EinsteinVisionAccessTokenManager.updateAccessToken
(accessTokenObject.accessToken);
}

Now that we have a mechanism to store and access the access token, we are ready to experiment with other Einstein APIs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset