Start building
Updates

How to set up Custom Identity Provider [Tutorial]

Igor Bojczuk, Jul 6, 2022


The main idea behind the Custom Identity Provider (CIP) is sharing the LiveChat chat history between multiple devices and sessions. This way, our user (provided that we have some identity management system in place) will be able to always access their chat history from any device/browser. The method requires a backend server, and in this tutorial, we will try to build a simple system utilizing this method.

LiveChat Customer Identity Provider synchronization illustration

There are no limitations on what technology is used for the backend, but in this demo, we will use a simple node-js/express server. We will also use a simple JSON database using a node-json-db package for database storage.

Prerequisites

First, let us understand how the Custom Identity Provider works.

In a typical case, with no CIP system enabled, LiveChat, when initializing on the page, will:

  • Check if lc cookies are set.
  • Send a request to the customer/token endpoint.
  • Make the response set new cookies in the headers. If the cookies were sent in the request, the token would be generated for the already-existing client (entityId). If not, a new customer will be generated.

The process doesn’t change when using the CIP. We’ll still play around with cookies, but this time with both hands on the steering wheel.

As you know, the LiveChat script will create a __lc object on our website, and before sending a request to the customer/token, it will check if that __lc object has a custom_identity_provider property, which we will have to supply. This property will consist of four functions:

window.__lc.custom_identity_provider = function () {
    return {
        getToken: ...,
        getFreshToken: ...,
        hasToken: ...,
        invalidate: ...
    }
}

Our backend will supply these four functions, and in this tutorial, we will show you how that's done.

LiveChat App

To properly fetch customer tokens, we need the client_id parameter that can be obtained when creating a new LiveChat App via the Developer Console. More info on that here: Building LiveChat apps.

Four Riders of Identity

Our backend server will supply the values for these properties, and these should be as follows:

  • getToken Chat Widget Token - token fetched from our database.
  • getFreshToken Chat Widget Token - fresh token fetched from customer/token endpoint for a specific entity_Id.
  • hasToken Boolean - a confirmation that a token exists for that specific customer.
  • invalidate null - a call to remove the cached version of our token, which we will not need in our example, hence this method will contain the placeholder Promise.resolve().

Chat Widget token

LiveChat authorization token you want to resolve in functions should contain the following properties:

ParameterDescription
accessTokenA token you can use to call LiveChat APIs on behalf of the user (customer).
entityIdThe ID of the customer.
expiresInA number in milliseconds specifying how long the accessToken will be valid.
tokenTypeValue: Bearer.
creationDateUnix timestamp specyfing the time of creation of the token.
licenseIdLiveChat license ID.

An example of properly constructed Chat Widget Token object is given below:

const chatWidgetToken = {
  accessToken: '<access-token>',
  entityId: '<entity-id>',
  expiresIn: 14400,
  tokenType: 'Bearer',
  creationDate: 1719402901,
  licenseId: 12345678,
}

Backend Plan

We will define three endpoints here:

  1. Check if the user already exists in our DB.

    • If the user is unknown, we will fetch a fresh Token from the <https://accounts.livechat.com/customer/token> endpoint, parse the response (including cookies) received, and save everything to our database.
    • If the user is known, we will fetch the __lc_cid, and __lc_cst cookies from our database and attach them to a customer/token request. This way, we will receive a new token for an existing entityId. Next, we need to save the newly obtained data to our database.
  2. Here we only need to check if the user exists in our DB. If they do, we will pass the data we have to them. Otherwise, we will simply forward it to the getFreshToken/ endpoint.

  3. This endpoint will return true if the user is in our DB and false otherwise.

The Backend Code

There are countless implementation possibilities, and everything will depend on how your project is structured. We will simply pass the "username" as the request parameter in this demonstration. However, for your implementation (and obvious security reasons), you might want to use unique IDs and Access Tokens so that your API is protected.

const express = require('express')
const axios = require('axios') //could be any other package capable of http calls
const cors = require('cors') //We are going to communicate via frontend with our API, therefore we need to handle CORS as well

require('dotenv').config()

//database configuration
const { JsonDB } = require('node-json-db')
const { Config } = require('node-json-db/dist/lib/JsonDBConfig')

var db = new JsonDB(new Config('myDataBase', true))

const app = express()
app.use(cors())

//liveChat configuration
var licenseId = parseInt(process.env.LICENSE_ID)
var clientId = process.env.CLIENT_ID

app.get('/getToken/:username', async (req, res) => {
  var getUserData = () => {
    try {
      //STEP 1. FIND IF THE USER IS ALREADY REGISTERED
      return db.getData('/' + req.params.username)
    } catch (err) {
      console.log(err.message)
      return null
    }
  }

  var userObject = getUserData()
  if (userObject) res.send(userObject)
  else res.redirect('/getFreshToken/' + req.params.username)
})

app.get('/getFreshToken/:username', async (req, res) => {
  var getUserData = () => {
    try {
      //STEP 1. FIND IF THE USER IS ALREADY REGISTERED
      return db.getData('/' + req.params.username)
    } catch (err) {
      console.log(err.message)
      return null
    }
  }

  var userObject = getUserData()

  const config = {
    method: 'POST',
    url: '<https://accounts.livechat.com/customer/token>',
    headers: {
      'Content-Type': 'application/json',
      origin: '<https://your-backend-url.com>', //an URL whitelisted in our app settings
      //STEP 2. ATTACH THE COOKIES TO THE REQUEST
      cookie: userObject ? [userObject.__lc_cid, userObject.__lc_cst] : null,
    },

    data: {
      grant_type: 'cookie',
      response_type: 'token',
      client_id: clientId,
      license_id: licenseId,
    },
  }

  //STEP 3. MAKE THE REQUEST CALL
  var response = await axios(config)

  var __lc_cid = response.headers['set-cookie'].find((el) =>
    el.includes('__lc_cid')
  )

  var __lc_cst = response.headers['set-cookie'].find((el) =>
    el.includes('__lc_cst')
  )

  //STEP 4. UPDATE DATABASE WITH NEWLY OBTAINED DATA
  var token = {}
  token.creationDate = Date.now()
  token.licenseId = licenseId
  token.entityId = response.data.entity_id
  token.expiresIn = response.data.expires_in
  token.tokenType = response.data.token_type
  token.accessToken = response.data.access_token
  token.username = req.params.username
  token.__lc_cid = __lc_cid
  token.__lc_cst = __lc_cst
  db.push('/' + req.params.username, token)

  //STEP 5. FORWARD THE RESPONSE

  res.json(token)
})

app.get('/hasToken/:username', async (req, res) => {
  //IF RECORD FOR THE USER EXISTS -> RETURN TRUE
  try {
    await db.getData('/' + req.params.username)
    res.send(true)
  } catch {
    res.send(false)
  }
})

app.get('/invalidate/:username', (req, res) => {
  //REMOVE THE USER RECORD
  try {
    db.delete('/' + req.params.username)
    res.send(true)
  } catch {
    res.send('errors when removing the user')
  }
})

//START THE SERVER
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
  console.log(`Our app is running on port ${PORT}`)
})

Frontend

Now we can easily connect everything on the front end side. This will be an incredibly straightforward system without a cache. However, you might want to save the Tokens in the customer's browser to minimize external API calls if that concerns you.

  1. We need to include the classic LiveChat Script on our webpage.
<script>
    window.__lc = window.__lc || {};
    window.__lc.license = YOUR_LICENSE_ID
    ;(function(n,t,c){function i(n){return e._h?e._h.apply(null,n):e._q.push(n)}var e={_q:[],_h:null,_v:"2.0",on:function(){i(["on",c.call(arguments)])},once:function(){i(["once",c.call(arguments)])},off:function(){i(["off",c.call(arguments)])},get:function(){if(!e._h)throw new Error("[LiveChatWidget] You can't use getters before load.");return i(["get",c.call(arguments)])},call:function(){i(["call",c.call(arguments)])},init:function(){var n=t.createElement("script");n.async=!0,n.type="text/javascript",n.src="<https://cdn.livechatinc.com/tracking.js",t.head.appendChild(n)}};!n.__lc.asyncInit&&e.init(),n.LiveChatWidget=n.LiveChatWidget||e}(window,document,[].slice))>
</script>
  1. In a separate script file (added below the LiveChat script, as it needs to load first on our page.), we can include our custom_identity_provider method. Our username will be set as a query parameter ?user=USERNAME, or a random ID if the query param is missing.
///1. GRAB THE USERNAME
const queryString = window.location.search
const urlParams = new URLSearchParams(queryString)
var user =
  urlParams.get('user') ||
  Date.now().toString(36) + Math.random().toString(36).substring(2)

///2. ADD THE CUSTOM IDENTITY PROVIDER
window.__lc.custom_identity_provider = () => {
  return {
    getToken: () => getToken(),
    getFreshToken: () => getFreshToken(),
    hasToken: () => hasToken(),
    invalidate: () => Promise.resolve(),
  }
}

var baseAPI = 'YOUR_API_URL'

///3. CONNECT THE CIP METHODS TO OUR API

const getToken = async () => {
  var apiURL = baseAPI + 'getToken/'
  var tokenObject = await fetch(apiURL + user).then((res) => {
    if (res.status < 400) return res.json()
    else return null
  })
  console.log('getToken', tokenObject)
  return tokenObject ? tokenObject : false
}

const getFreshToken = async () => {
  var apiURL = baseAPI + 'getFreshToken/'
  var tokenObject = await fetch(apiURL + user).then((res) => {
    if (res.status < 400) return res.json()
    else return null
  })
  return tokenObject ? tokenObject : false
}

const hasToken = async () => {
  var apiURL = baseAPI + 'hasToken/'
  var response = await fetch(apiURL + user).then((res) => res.json())
  response = JSON.stringify(response)
  return response === 'true'
}
  1. And that's it! You got yourself a working LiveChat integration with a Custom Identity Provider. This way, your customers will always have access to their chats’ history.

GitHub

The entire code for this tutorial is available on GitHub.

Check the docs

Latest articles

Article banner: What Do Software Engineers Do? Roles, Responsibilities, and Skills Explained

Dec 13, 2024

What Do Software Engineers Do? Roles, Responsibilities, and ...

Article banner: How to Get Started in Cyber Security: A Beginner’s Guide

Dec 6, 2024

How to Get Started in Cyber Security: A Beginner’s Guide

Article banner: What is Binary Code? Modern Language to the Binary System

Nov 20, 2024

What is Binary Code? Modern Language to the Binary System