Few weeks ago I blogged about Dealing with the running User on Einstein Bot dialogs.

One of the questions I got from the community was:

is this approach safe for production?

I was actually not sure about this, as the logged in info was passed from the pre-chat page to the chat bot context in clear: this way an advanced JS hacker could have impersonated a real user, knowing its username/user id: that’s why I replied that you should have used a temporary “token”, that should have been deleted once used (upon user confirmation on the Chat Bot loading step).

When I write posts about tech workaround all I have in mind is open your minds and make you find a new way to solve problem: from this point we can enhance our solution to make it the best one!

Few days ago Arthur Imirzian contacted me to read his new article on his blog: How to safely get the logged in community user from Einstein Bot.

This blog was the direct consequence of my workaround, making the user identification safer.
He then went on by finding another workaround to enforce user authentication with this great post!

I loved his quick and dirty style and I decided, upon his approval, to repost his article here.

Enjoy the reading!


Kerckhoffs’s principle says that a cryptosystem should be secure even if everything about the system, except the key, is public knowledge. The user id is not a key and shouldn’t be considered as one.

Both classic Live Agent client and Snap-Ins chat client provide a very useful feature to pass the prechat information to the custom fields on LiveChatTranscript object.

As you know, Einstein bot is built on top of Live Agent and allows you to trigger apex actions based on community user message like a knowledge article search. Problems come when you want to make authenticated actions like updating your case status or contact information. Live Agent doesn’t provide any way to enforce logged community user session. Instead, the context user is a technical user (AutomatedProcess or Integration).

The first reflex is to get the user id from a visualforce page / lightning component and pass it to the Snap-Ins Code Snippet in order to reuse it from apex by querying the Transcript Object. This approach is a security breach as the user id can be easily changed on the fly. The attacker can usurp any user by providing another user id. Again, user id is not a private key and shouldn’t be used as one.

A safer approach

So we need to find a way to validate that the returned user id is equal to the connected user id. The idea illustrated below suggest asymmetrically hashing the user id so no one can change it to usurp someone else’s identity. As the end user cannot produce a token, it can’t corrupt the user id.

Step 1: Token generation

The first step is about token generation based on connected user id. The user id will be publicly accessible but we will use a self-signed certificate to sign the user id and generate a token.

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.

Use the apex code below to generate a secure token based on session id and user id of the connected user.

public static String getToken(String keyToSign){
    if(keyToSign == null){
        return null;
    }
    return EncodingUtil.base64Encode(
        Crypto.signWithCertificate(
            'RSA-SHA256',
            Blob.valueOf(keyToSign),
            'LightningComponent'
        )
    );
}

Step 2: Get the user id and the token

Add the lightning:prechatUI interface to a Lightning component to enable Snap-ins to identify it as a custom Lightning page template that can be used for the Snap-ins Chat pre-chat page (docs here).

Now call the getPrechatData method from your lightning component to retrieve the logged user id and its token.

@AuraEnabled
public static Map<String,String> getPrechatData() {
    Map<String,String> prechatData = new Map<String,String>();
    // Returns the context user's ID
    String userId = UserInfo.getUserId();
    prechatData.put('userId',userId);
    String token = getToken(userId);
    prechatData.put('token',token);
    return prechatData;
}

Step 3: Pass the prechat information

LiveChatTranscript is the unique object that can be used to share context between the logged user and Eintein Bot which means that we need to create custom fields on LiveChatTranscript object.

Go to Setup > Object Manager and search for the Live Chat Transcript object. Then Field & Relationships to create two new custom fields called UserId (UserId__c) and Token (Token__c).

Now we have retrieved the user id and the token from step 2, we can pass them through a snippet setting file.

Add the code below to your existing Snippet Settings File you had uploaded as a static resource.

embedded_svc.snippetSettingsFile.extraPrechatInfo = [
    {
        "label":"UserId",
        "transcriptFields":[ "UserId__c" ],
        "displayToAgent": false
    },
    {
        "label":"Token",
        "transcriptFields":[ "Token__c" ],
        "displayToAgent": false
    }
];

Don’t hesitate to visit Nerd At Work’s blog for more details.

Step 4: Validate the token and trust the user id

This is the last step before we can rely on the returned user id. We need to ensure that nothing has been corrupted. We’re going to sign the user id returned by the lightning component again and compare this new token to the returned token.

public static Boolean validateToken(String userId, String token){
    return token == getToken(userId);
}

If the user id was corrupted by the end user, signing it again will produce a different token than the previous one. If not, it means we can now rely on it.

Conclusion

We just described how to safely get the connected community user id from a live agent session. And remember you should never trust user input, your user will not always submit data your application will expect.

What’s the next step ? First pass the Einstein Bots Basics module and then jump to the second article about authenticated DML operation from Einstein BOT.