How to enforce logged in community user context from Einstein Bot

Prerequisites

If you have no idea about how you can safely get the logged in community user id, please read carefully and follow the 4 steps of the article below.

Passing the user id from the front page without any security validation is a security breach as the user id can be easily changed on the fly.

Don’t trust user input

The current situation

As we safely got the user id from Snap-Ins/Live Agent client it’s time to perform some SOQL/DML operations from your Apex class. Open your Einstein Bot, select the dialog from the left-side panel. Click on the plus button from the dialog layout and select Action.

Don’t forget to add the apex class to the permission set sfdc.chatbot.service.permset otherwise you will encounter some problems. Another point, about the API version of your class, or any classes with a low API version called by Einstein Bot context will fail if you use the UserInfo.getUserId(). To fix this issue, simply update the API version of your classes to something greater than 42.

The context user is not the one connected to the community but a technical user meaning nor the right sharing rules nor the right business rules are applied.

public with sharing class BOT_UpdateContact {

/* InvocableVariables here */
    @InvocableMethod(label='Update Contact Information')
public static List<ContactOutput> updateContact(List<ContactInput> contactInputs){
/* DML operation here */
}
}

The system method runAs enables you to change the user context to an existing user or a new user so that the user’s record sharing is enforced but you can use runAs only in test methods. So it leaves us one solution: use an internal authenticated web service.

Setting up the workaround

So instead of calling the DML from the Einstein Bot context, we will call an internal apex rest service to apply the right context.

Step 1 : The self-signed certificate

Go to Setup | Security | Certificate and Key Management | Create Self-Signed Certificate, define a label, a unique name, uncheck Exportable Private Key and click on save.

Then open the newly created certificate and click on Download Certificate to download it.

Step 2 : The Connected App

Go to Setup | Apps | App Manager | New Connected App and check Use digital signature to upload the certificated you downloaded at step 1.

Fill all the other required information to create the connected app and don’t forget to allow community user profiles to access it and set the policy to Admin approved users are pre-authorized. Then copy the Consumer Secret, we’ll need it later.

Step 3 : The JWT token

The last step is about getting an access token that authenticate the connected community user. To do so, we’ll use the OAuth 2.0 JWT Bearer Token Flow. JWT flow is a server to server authentication flow. Use the private key to sign the json and post the JWT to the OAuth token endpoint, which in turn processes the JWT and issues an access_token (but no refresh_token).

First we’ll use the trusted user id to query the community user’s username. Then use the method below to get an access token. You’ll need to update siteBase and consumerKey parameters.

public class Bot_JWT {
public static String getAccessToken(String username){
String siteBase = 'https://***.force.com';
String consumerKey = '3MV..***..0Ln8ievjdl';
Auth.JWT jwt = new Auth.JWT();
jwt.setSub(username);
jwt.setIss(consumerKey);
jwt.setAud(siteBase);
Auth.JWS jws = new Auth.JWS(jwt, 'LightningComponent');
String tokenEndpoint = siteBase+'/services/oauth2/token';
Auth.JWTBearerTokenExchange bearer = new
Auth.JWTBearerTokenExchange(tokenEndpoint, jws);
String accessToken = bearer.getAccessToken();
return accessToken;
}
}

Conclusion

You can now use apex DML actions as the logged in community user (and not only as the integration user).