Anonymous Visitors and Authenticated Customers

In this article, we'll discuss the difference between anonymous visitors and authenticated customers using your Live Chat web widget.

Live Chat - customer authentication process

Anonymous Visitors

By default, all visitors can see your website widget. You can change this by configuring and customizing who will see your live chat widget.

Visitors will also be able to see their chat history once they’ve initiated a conversation. However, for security reasons, we’ve introduced the chat session timeout.

Each anonymous visitor session can be personalized with a personalization token. Once the widget session is signed with the personalization token, it becomes a trusted session with an authenticated customer.

The website backend application controls trusted sessions and can interrupt the session on any logic that the website backend application has. For example, when a customer logs out, the website’s backend application should terminate this trusted session.

Find out more on how to authenticate customers in the Customer Authentication Setup section.

Authenticated Customers

Once you add the web widget to your website, by default, all visitors and customers who initiate a conversation over that widget are flagged as anonymous visitors.

However, you can configure the web widget to identify authenticated customers if they are logged in based on your website authentication logic.

Authenticated customers are customers who logged in to your website. Authenticated customers can see their entire conversation history within the widget once they log in.

Conversations started by the anonymous visitor can be converted into a conversation with an authenticated user once this customer logs in to your website. Then the entire conversation will be stored as an authenticated customer chat session.

NOTE

Infobip does not authenticate your customers through Live Chat. Customer authentication is executed within your website’s backend application.

Here’s a high-level overview on how to enable authenticated customer chat session from your website:

Step 1 - Generate a secret key at your widget configuration. 

Step 2 – Implement personalization token generator on your website backend application

Step 3 - Add Widget API auth method for your authenticated customers chat session

Step 4 - Manage your authenticated customers logout

Enable Authenticated Customers

This  section explains how the Live Chat customer authentication process works.

To set up the customer authentication, follow these steps:

1. Generate Secret Key

The secret key is a special encryption key used to generate the personalization token. For security reasons, we recommend you keep this secret key safe to avoid data leakage.

You can have as many secret keys as you need. Each secret key is unique for each widget and can be associated only with a specific widget ID.

To generate a secret key, navigate to Apps > Live Chat, select a widget to edit, then navigate to the Installation & Security section and click GENERATE NEW KEY.

Live Chat - Generate secret key

Here’s how the secret key looks like once it’s generated:

Live Chat - Generate secret key ID

Copy the key to your clipboard (here's an example):

{"id":3,"key":"7Q25YT4fKM7G+BO/7QyW9vdF/YC8zBN3w4HQPyKgk98="}

NOTE

You need to bear in mind that the secret key is Base64 string representation.

Each secret key contains the following information:

  • the date of creation
  • the time it was last used (the last time the widget had signed a conversation with this security key)

2. Personalization Token Generator

The personalization token is based on JSON Web Token (JWT) technology. Within your application backend, you need to implement the JWT personalization and sign in with the secret key you previously copied.

Infobip supports the HMAC encryption algorithm verification (default HS256), a worldwide popular encryption algorithm. This is a standardized snippet to generate a token with additional parameters.

ALERT

Personalization token can be used only once to sign in the customer authentication chat session. Authenticated chat session will be automatically maintained by Infobip web widget until you invalidate the session with the widget logout method or invalidate session API call.

To generate a token, you need the following:

  • Customer’s unique identifier (phone number, email or externalUserID
  • Secret key (secret key ID)

Output from snippet:

  • ID – Personalization token ID
  • Personalization token

Here are the parameters to form a JWT payload with personalization parameters data from your backend:

Parameter

Mandatory

Description

alg

yes

Encryption algorithm. Supported value HS256

exp

optional

Token expiration time. POSIX time format in SEC. The default value is 15 seconds. Personalization token is for single use only and its “short-lived” to keep you secure. Less than 10 seconds is not recommended.

iat

yes

Creation date. POSIX time format in SEC.

iss

yes

Widget ID is a unique identifier of the widget configuration. Can be copied from widget snippet or directly from your widget configuration 

jti

yes

Generic ID of personalization token. You can put any string here. You must not use tokes with the same ID, they should always be unique. Personalization token ID (JTI), max length: 50 characters.

sid

optional

Session ID. It’s your unique generated session ID which can be used for session invalidation API. For each customer login, the session SID parameter should always be unique. 

Session ID (SID), max length: 50 characters.

If you rely only on the logout method of Widget API, you can skip this parameter.

ski

yes

ID of your generated security key. 

stp

yes

Should be explicitly defined as a type of 'sub' value. Available types:

  • email
  • msisdn
  • externalPersonId

Strict naming shall be used.  

sub

yes

You customer's unique identifier. Can be email or phone number or your unique external ID. For example, john.smith@domain.com

Example

 alg: "HS256"
}.
{
 jti: "f69fbb80-2967-4985-afae-6cfe6c0786c4",
 sub: "john.smith@domain.com",
 stp: "email",
 iss: "e7de374f-e590-4429-ae2d-54be7e90a356",
 iat: 1582700204,
 exp: 1582700264,
 ski: "52",
 sid: "85a53925-7bbb-46be-84f8-2b00c4a48a4d"
}.
xqDTU5RYS-KJRL7zyuwra2PDfZBNDHI3bbZHHrjQUjA
Example of a generated personalization token

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJLb25zdGFudGluLnZhbGlvdHRpQGluZm9iaXAuY29tIiwia2lkIjozLCJpc3 MiOiJDaGF0VGVzdEFwcENvZGUiLCJpYXQiOjE1ODI5MDI1MzcsInNrdCI6ImVtYWlsIiwianRpIjoiODVhNTM5 MjUtN2JiYi00NmJlLTg0ZjgtMmIwMGM0YTQ4YTRkIn0.xqDTU5RYS-KJRL7zyuwra2PDfZBNDHI3bbZHHrjQUjA

3. Enable Authenticated Customer Session With Widget API

All Widget API follows the structure:

liveChat(command [, input] [, output handler callback function]);

The callback parameter is optional for handling errors from Live Chat Widget (no connection, connection lost, bad request, etc). For all commands, input is always required.

On your website customer login page, sign the chat session with your authenticated customer date by widget JS API:

liveChat('auth', '[personalizationToken]');

 

Example of 'auth' method with callback:

 

liveChat('auth', '[personalizationToken]', function(error, result) {
  if (error) {
    ... // do process error
    console.log(error.code, error.message)
  } else {
  ... // no errors
  }
});

Possible error codes:

Code Main error Message
1101 mandatory field missing parameter 'token' is required in the method
1102 mandatory field missing 'ski' field is required in JWT
1103 mandatory field missing 'sub' field is required in JWT
1104 mandatory field missing 'iss' field is required in JWT
1105 mandatory field missing 'iat' field is required in JWT
1106 mandatory field missing 'jti' field is required in JWT
1111 field in wrong type 'iat' should be a 'number' type, and should be in seconds
1112 field in wrong type 'exp' should be a 'number' type, and should be in seconds
1113 field in wrong type 'stp' should be one of ['email', 'msisdn', 'externalPersonId']
1121 method invocation error user is already authenticated
1122 method invocation error JWT payload is broken
1123 method invocation error 'ski' is wrong, no widget key with this id
1124 method invocation error 'alg' is not correct
1125 method invocation error something wrong with encryption
1126 method invocation error 'iss' differs from initialized widget id
1198 connection issue failed to auth. Please try again later.
1199 request error Request failed with status ${status}

4. Invalidate Authenticated Customer Session

Here's how to invalidate an authenticated customer session.

Logout Widget API for Authenticated Session Invalidation

In cases where the end customer explicitly logs out, use the widget API logout method:

liveChat('logout', [callback]);

Example:

liveChat('logout', null, function(error, result) {
  if (error) {
    ... // do process error
    console.log(error.code, error.message)
  } else {
  ... // no errors
  }
});

Possible errors:

Code Main error Message
1321 method invocation error user is already logged out

Session Invalidation With Infobip Public API

As an alternative to the logout method in widget API, you can use invalidation session API on Infobip public API. Invalidation session API can only be used only if back in the personalization token generator you've added the SID parameter.

When a specific session ID (SID) invalidation call is performed, the chat session in widget is immediately reset to the anonymous session. The reset session erases the message history until the next login of the authenticated customer.

5. Personalization Token Generator Code Examples

You can use one of the prepared code examples for your backend services to generate personalization token for your authenticated customers in Infobip Live Chat widget.

REMINDER

Do not forget to decode from Base64 'key' value before signing token!

Examples of personalization token generator:

  • Kotlin
  • Java
  • PHP
  • NodeJS

PHP Implementation

// Download PHP-JWT: https://github.com/firebase/php-jwt
composer require firebase/php-jwt
use \Firebase\JWT\JWT;
$timestamp = time();
$securityKey = json_decode('Copied data from Infobip Live Chat Security Key');
$payload = {
  'jti' => 'random UNIQUE String',
  'stp' => '[email | msisdn | externalPersonId]',
  'sub' => '[email.@email.com | +PhoneNumber | ExtPersonID]',
  'iss' => 'Widget ID',
  'iat' => $timestamp,
  'exp' => $timestamp + 15,
  'ski' => $securityKey->id,
  'sid' => 'Session Identifier'
};
 $token = JWT::encode($payload, base64_decode($securityKey->key));

NodeJS Implementation

// Install jsonwebtoken: npm install jsonwebtoken --save-dev
var jwt = require('jsonwebtoken');
const securityKey = JSON.parse('Copied data from Infobip Live Chat Security Key');
const timestamp = Math.floor(date.getTime()/1000);
var payload = {
  jti: 'random UNIQUE String',
  stp: '[email | msisdn | externalPersonId]',
  sub: '[email.@email.com | +PhoneNumber | ExtPersonID]',
  iss: 'Widget ID',
  iat: timestamp,
  exp: timestamp + 15,
  ski: securityKey.id,
  sid: 'Session Identifier'
};
var token = jwt.sign(payload, Buffer.from(securityKey.key, 'base64'));

JAVA Implementation

import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.*;

@Component
class PersonalisationTokenGenerator {
  // IMPORTANT: securityKey here it is a copied data from Infobip Live Chat Security Key
  public ResponseEntity<String> authorize(String widgetId, String securityKey) {
    try {
      // simple example how personalizationToken issuing process could looks like:
      ObjectMapper objectMapper = new ObjectMapper();
      SecurityKeyHMAC secret = objectMapper.readValue(securityKey, SecurityKeyHMAC.class);
      MACSigner personalizationTokenSigner = new MACSigner(Base64.getDecoder().decode(secret.getKey()));
      JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
        // generate always UNIQUE ID because Infobip will remember it and not permit to use it twice! As ID can be used ant string
        .jwtID(UUID.randomUUID().toString())                                      // unique ID of this token. Mandatory
        .subject("example-email@infobip.com")                                     // person destination in Infobip People service. Mandatory
        .issuer(widgetId)                                                         // which widget it belongs. Mandatory
        .issueTime(new Date())                                                        // when created. Mandatory
        .expirationTime(Date.from(Instant.now().plusMillis(15000))) // Optional, e.g. when this token should be invalidated if not yet used,
        // if not set then by default token will be invalidated after 15 seconds after issueTime
        .claim("ski", secret.getId())                                                 // needs for security validation. Mandatory
        .claim("stp", "email")                                                   // type of person destination in Infobip People service. Mandatory
        .claim("sid", "Session identifier")                                      // unique session identifier. Optional
        .build();
      SignedJWT personalizedToken = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);
      personalizedToken.sign(personalizationTokenSigner);
      return ResponseEntity.ok(personalizedToken.serialize());
    } catch (Exception ex){
      // do some logging stuff
      ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
  }
}

class SecurityKeyHMAC {
  private long id;
  private String key;

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  public String getKey() {
    return key;
  }

  public void setKey(String key) {
    this.key = key;
  }
}

Kotlin Implementation

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.JWSHeader
import com.nimbusds.jose.crypto.MACSigner
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Component
import java.time.Instant
import java.util.*

@Component
internal class PersonalisationTokenGenerator {
    // IMPORTANT: securityKey here it is a copied data from Infobip Live Chat Security Key
    fun authorize(widgetId: String?, securityKey: String?): ResponseEntity<String> {
        try {
            // simple example how personalizationToken issuing process could looks like:
            val objectMapper = jacksonObjectMapper()
            val secret = objectMapper.readValue(securityKey, SecurityKeyHMAC::class.java)
            val personalizationTokenSigner = MACSigner(Base64.getDecoder().decode(secret.key))
            val claimsSet = JWTClaimsSet.Builder()                          // generate always UNIQUE ID because Infobip will remember it and not permit to use it twice! As ID can be used ant string
                .jwtID(UUID.randomUUID().toString())                        // unique ID of this token. Mandatory
                .subject("example-email@infobip.com")                       // person destination in Infobip People service. Mandatory
                .issuer(widgetId)                                                            // which widget it belongs. Mandatory
                .issueTime(Date())                                          // when created. Mandatory
                .expirationTime(Date.from(Instant.now().plusMillis(15000))) // Optional, e.g. when this token should be invalidated if not yet used,
                // if not set then by default token will be invalidated after 15 seconds after issueTime
                .claim("ski", secret.id)                                    // needs for security validation. Mandatory
                .claim("stp", "email")                                      // type of person destination in Infobip People service. Mandatory
                .claim("sid", "Session identifier")                         // unique session identifier. Optional
                .build()
            val personalizedToken = SignedJWT(JWSHeader(JWSAlgorithm.HS256), claimsSet)
            personalizedToken.sign(personalizationTokenSigner)
            return ResponseEntity.ok(personalizedToken.serialize())
        } catch (ex: Exception) {
            // do some logging stuff
            ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build<Any>()
        }
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build()
    }
}

data class SecurityKeyHMAC(val id: Long, val key: String)