Widget authentication
kindly supports authenticating your users so they can view or manage their accounts, orders, bookings, etc from the chat it also allows your live agents to know who they are talking to we support three different ways to configure authentication kindly jwt verifies tokens you sign yourself with a pem public key jwks provider verifies tokens issued by an openid connect or oauth 2 0 identity provider, or any other issuer that publishes a jwks key set introspection validates opaque bearer tokens against your endpoint (rfc 7662) you can configure more than one, of any type each added method appears in the configured authentication methods table (provider name, type, issuer) and can be edited or deleted from there all methods result in the same outcome user details appear in your live agent platform, and the token is forwarded to your webhooks so you can identify the user server side more information about each method type below which method should i use? kindly jwt jwks provider introspection description verifies tokens you sign yourself with a pem public key verifies tokens issued by an oidc or oauth 2 0 identity provider, or any other jwks compatible issuer validates opaque tokens against your endpoint (rfc 7662) you already run an oidc or oauth 2 0 identity provider works, but adds a parallel token scheme ✅ easiest option ✅ identity providers often provide an introspection endpoint out of the box your access tokens are opaque or non standard jwts ❌ ❌ (when using an id token and an access token, the access token can be opaque) ✅ you want kindly to verify tokens without you managing a key pair ❌ (you manage the rsa key pair) ✅ ✅ you want full control over custom claims in the token ✅ limited to your provider's claims limited to your endpoint's introspection response authentication strategies once a method resolves, how kindly verifies the user comes down to what you hand the chat client you don't select a strategy explicitly kindly picks it from the token(s) it receives what you return how kindly verifies it set up with single jwt one jwt kindly jwt rs256 against your public key jwks provider signature check against your provider's jwks kindly jwt / jwks provider dual token an id token (jwt) and a separate access token (which may itself be a jwt or opaque) id token via jwks; access token via jwks if it's a jwt, or stored unverified alongside the verified id token if it's opaque jwks provider single opaque token one bearer token that isn't a standard jwt (opaque, or a jwt shaped token only your own endpoint can validate) token introspection (rfc 7662) against your endpoint introspection each setup section below includes a sequence diagram of its flow kindly jwt, jwks provider, and introspection authentication strategies navigate to your workspace's settings → kindly chat → authentication to set any authentication provider set up "kindly jwt" click set up on the kindly jwt card to open the kindly jwt verifier dialog required description issuer url (iss) yes must match the iss claim your backend puts in the jwt payload (see token format below) you can add multiple kindly jwt verifiers, each scoped to a different issuer public key yes the pem encoded public key matching the private key your backend signs with the rest of this section covers generating the key pair, minting the jwt, and wiring up the widget callback sequencediagram participant u as user participant c as kindly chat client participant b as your integration backend participant k as kindly u >>c start a chat c >>b getauthtoken(chatid) b >>c jwt signed with your private key c >>k authenticate the chat k >>k verify with your public key k >>c avatar url, full name opt webhook dialogue c >>k trigger a webhook dialogue k >>b webhook request with forwarded jwt b >>b verify jwt with your public key b >>k webhook response k >>c text response end create a private/public key pair to allow your own server to authenticate webhook requests without the kindly api interfering, we use a standard private/public key pair the private key is used by your server to encode and sign the jwt in your authentication endpoint the public key goes into the public key field of the kindly jwt verifier above; kindly uses it to verify that the jwt is valid run the following commands to generate a key pair on a linux or unix like environment openssl genrsa out private pem 2048 openssl rsa in private pem outform pem pubout out public pem the contents of private pem should look something like \ begin rsa private key miieowibaakcaqeasufro2/65cn2vlsqjydedgfwnivezqowut0z7qhzcdhbq2iv +8l4ahkephz1lzs2/fejkrtmctprr/jcrvbjgf+shbw0hnsamovsvapy6k1w6qul \ end rsa private key and the contents of public pem should look like this \ begin public key miibijanbgkqhkig9w0baqefaaocaq8amiibcgkcaqeasufro2/65cn2vlsqjyde dgfwnivezqowut0z7qhzcdhbq2iv+8l4ahkephz1lzs2/fejkrtmctprr/jcrvbj \ end public key create an authentication endpoint the responsibility of authenticating your users is yours and is usually done by an http endpoint you implement in the most common case, the kindly chat callback sends a http post request to an authentication endpoint you implement if the request is sent from the same domain as the endpoint, cors headers can be omitted create an authentication endpoint that returns a jwt in the format specified below be aware that jwts are encoded and signed, not encrypted, meaning anyone who has the token can read the content set up an authentication endpoint on your backend that returns a json web token (jwt) signed using the rs256 algorithm and the private key created in the step above, and the payload must conform to the following required format you can implement the auth endpoint however you wish we recommend using a compatible jwt library from the list at https //jwt io/ https //jwt io/ token format required description iat yes integer timestamp (epoch time) when the token is generated exp yes integer timestamp (epoch time) when the token expires must be no more than 15 minutes after iat this is separate from the freshness window below even a token set to expire in 15 minutes is only accepted within 10 seconds of iat iss yes string representing the issuer of this token must match the issuer url (iss) you configured on the kindly jwt verifier sub yes string representing your id for the logged in user chat yes a json object containing the listed keys example { id "abc123", webhook domains \["example com", " example org"] } chat id yes string value provided by kindly chat, representing the current chat chat webhook domains array of hostnames kindly is allowed to forward the token to, e g example com, or example org to cover subdomains hostnames only (no scheme, port or path), and one malformed entry rejects the whole token sid string id for the user's session on your side only used with backchannel logout (see below), where it lets a logout request clear the one session it names without it, a logout clears every chat bound to that sub for this issuer name string value representing the full name of the user displayed in kindly chat and inbox email string value representing the email of the user displayed in kindly chat and inbox email verified boolean value indicating whether you have verified the email the email is stored either way; this flag is recorded alongside it phone number string value representing the phone number of the user displayed in kindly chat and inbox phone number verified boolean value indicating whether you have verified the phone number the phone number is stored either way; this flag is recorded alongside it picture an url, which is publicly accessible, pointing to a user avatar displayed in kindly chat and inbox you may also include any other property you want to use in your webhooks you can read more on jwt in the standard here mint a fresh token on every request a kindly jwt is only accepted for about 10 seconds after its iat; kindly rejects a token presented later than that as expired, regardless of the exp you set this is why getauthtoken is called on demand (and re run automatically before a session refresh) rather than returning a stored value never cache or reuse a kindly jwt across requests (this freshness window applies to the kindly jwt method only; jwks provider tokens are accepted up to their exp ) python example using django rest framework import datetime import jwt from django conf import settings from django http response import httpresponsebadrequest, jsonresponse from django utils import timezone from rest framework decorators import api view, permission classes from rest framework permissions import isauthenticated @api view(\['post']) @permission classes((isauthenticated,)) def auth(request) chat id = request data get('chat id') if not chat id return httpresponsebadrequest() now = timezone now() iat = int(now\ timestamp()) expires = int((now + datetime timedelta(minutes=1)) timestamp()) # jwt is valid for 1 minute user = request user payload = { 'iat' iat, 'exp' expires, 'iss' settings chat client auth issuer, # must match the issuer url (iss) on the verifier 'chat' {'id' chat id}, 'sub' str(user id), 'name' user get full name(), 'email' user email, 'email verified' true, 'picture' 'https //example com/avatar jpg', } \# pyjwt >= 2 0 returns a str; on pyjwt < 2 0, append decode('utf 8') token = jwt encode(payload, settings chat client auth private key, algorithm='rs256') return jsonresponse({'token' token}, status=200) set up jwks provider click set up on the jwks provider card to open the jwks provider dialog it works with any issuer that publishes a jwks key set, openid connect or any plain oauth 2 0 authorization server provider identity issuer url used to match incoming jwts (iss claim) required description issuer url (iss) yes your identity provider's issuer, e g https //idp example com/realms/my realm discovery url optional accepts an openid connect discovery document or an rfc 8414 oauth 2 0 authorization server metadata document only needed when it isn't at \<iss>/ well known/openid configuration or \<iss>/ well known/oauth authorization server jwks uri optional an alternative to discovery url fetches the key set directly, skipping metadata discovery prefer discovery url when your provider has one a metadata document lets your provider rotate its key set without a config change here use jwks uri for a provider that publishes a key set but no metadata document at all jwt token verification id/access tokens, verified via jwks required description allowed access token audiences yes audience (aud) on the token you send in authorization allowed id token audiences only if you also send a separate access token via x access token (dual token) audience(s) your provider puts on the id token, checked when a companion access token makes the authorization bearer an id token audiences are matched against the token's aud claim, and there is no way to opt out of the check a method with no audiences configured rejects every token set them to the value your identity provider actually puts in aud, normally your client id for a single jwt setup, allowed access token audiences is all you need; dual token setups need both fields, because the id token and the access token are checked separately avoid allow listing a broad, shared audience some providers put a built in audience on nearly every token they issue allow listing one of those accepts tokens minted for any other application at that issuer, not just yours outbound forwarding domains verified tokens may be forwarded to required description forward domains domains kindly is allowed to forward the token to in webhook requests, e g example com unioned with chat webhook domains if the token kindly forwards carries one (that's the access token in dual token setups, since the id token is never forwarded) with a jwks provider you can send either a single jwt (id token only) or two tokens (id token plus an access token) the access token can either be a jwt or an opaque token see authentication strategies a single opaque token instead uses the introspection method dual token both tokens must describe the same user when the access token is a jwt, its sub has to match the id token's sub; kindly rejects the pair otherwise, leaving the chat as it was an access token that identifies itself as an id token (a token use "id" claim) is rejected for the same reason opaque access tokens can't be checked this way, so make sure your backend pairs them per user sequencediagram participant u as user participant c as kindly chat client participant b as your integration backend participant k as kindly participant i as your identity provider u >>c start a chat c >>c getauthtoken(chatid) c >>i obtain token (directly, or via your backend) i >>c id token (+ access token for dual token) c >>k authenticate the chat k >>i verify via jwks k >>c avatar url, full name opt webhook dialogue c >>k trigger a webhook dialogue k >>b webhook request with forwarded token b >>i verify token via jwks b >>k webhook response k >>c text response end set up an introspection provider this is the single opaque token strategy your backend returns one bearer token that doesn't decode as a jwt (or a non standard jwt shaped token only your own endpoint can validate), and kindly validates it against your introspection endpoint click set up on the introspection card to open the introspection provider dialog provider identity required description provider id (iss) yes an identifier for this config opaque tokens don't carry an issuer claim, so your client must send this same value in its issuer field to select this provider token introspection oauth 2 0 token introspection (rfc 7662) works for opaque bearer tokens and non standard jwt shaped tokens validated via your introspection endpoint instead of jwks required description discovery url optional accepts an openid connect discovery document or an rfc 8414 oauth 2 0 authorization server metadata document lets kindly read introspection endpoint from it only needed when it isn't at \<iss>/ well known/openid configuration, \<iss>/ well known/oauth authorization server, or set directly in introspection url below introspection url only if provider id isn't an https url and discovery url is unset your oauth 2 0 introspection endpoint, e g https //auth example com/oauth/introspect introspection client id yes client id kindly authenticates with when calling your introspection endpoint introspection client secret yes client secret for the above jwt token verification id/access tokens, verified via introspection claim required description allowed access token audiences yes the introspection response's aud claim is checked against this list backchannel logout optional when not set, the key set is discovered via discovery url instead needed only if you plan to send kindly backchannel logout notifications required description jwks uri used to verify the signature of backchannel logout tokens outbound forwarding required description forward domains domains kindly is allowed to forward the token to in webhook requests, e g example com kindly calls your introspection endpoint with the bearer token (post, form encoded, with your client id and secret as http basic credentials) and requires active true, a sub (the user's id), and an aud matching allowed access token audiences in the response before treating the user as authenticated identity fields such as username and email are read from the same response if your response includes an iss, it must match the provider id (iss) you configured watch for this if you pick a non url provider id while your authorization server returns its real issuer url; the two won't line up and every authentication will be rejected either use the issuer url as the provider id, or omit iss from the response unlike the jwt based strategies, there's no signature to check locally for the token itself kindly validates it by calling your authorization server discovery url and jwks uri are the exception they exist only so kindly can verify the signature of a backchannel logout token, which your provider mints for that notification itself rather than issuing to a user sequencediagram participant u as user participant c as kindly chat client participant b as your integration backend participant k as kindly participant i as your authorization server u >>c start a chat c >>c getauthtoken(chatid) c >>i obtain opaque access token (directly, or via your backend) i >>c opaque access token c >>k authenticate the chat k >>i introspection request (rfc 7662) i >>k claims k >>c avatar url, full name opt webhook dialogue c >>k trigger a webhook dialogue k >>b webhook request with forwarded token b >>i introspect token i >>b claims b >>k webhook response k >>c text response end authenticate the chat client whichever method you configured above, you tell the chat client how to obtain a token with a getauthtoken function it's called with a single argument chatid and must return the token(s) for that chat getauthtoken can return either a plain string or a credentials object, depending on your setup return a string when a single token is enough this is the case for kindly jwt and single token jwks provider return a credentials object when you need to pass more than one token, or need to tell kindly which provider to use this is the case for dual token jwks provider and introspection required purpose idtoken yes the user's token a kindly jwt, a jwks provider id token, a single jwt, or an opaque access token accesstoken only for dual token jwks provider setups forwarded to your webhooks so you can identify the user server side issuer yes for introspection tells kindly which introspection provider to validate against must match the provider id (iss) you set above expiresat unix seconds set this if you know the expiry of an opaque token that kindly can't otherwise determine there are two ways to hand this function to the chat client define it up front so the user is authenticated as soon as the chat starts, or call authenticate() later once you know who the user is authenticate automatically when a chat starts define getauthtoken on window\ kindlyoptions before the chat script loads kindly calls it as soon as a chat starts, and again whenever the token needs refreshing returning a string \<script type="text/javascript"> window\ kindlyoptions = { getauthtoken async function (chatid) { return fetch("/your auth endpoint", { method "post", body json stringify({ chat id chatid }), // matches the auth endpoint examples above }) then((response) => response json()) then(({ token }) => token); }, }; \</script> \<script id="kindly chat" src="https //chat kindlycdn com/kindly chat js" data bot key="your bot key" async \>\</script> returning a credentials object \<script type="text/javascript"> window\ kindlyoptions = { getauthtoken async function (chatid) { const { idtoken, accesstoken } = await fetchtokensfromyourbackend(chatid); return { idtoken, // required the user's token accesstoken, // optional include for dual token setups // issuer 'my provider id', // required for introspection your // provider's provider id (iss) }; }, }; \</script> authenticate after a chat has started if the user isn't known when the chat loads (for example they sign in partway through the conversation), call window\ kindlychat authenticate() once they are pass it the same getauthtoken function; kindly calls it immediately and re authenticates the current chat // e g after the user logs in on your side window\ kindlychat authenticate(async function (chatid) { const { idtoken, accesstoken } = await fetchtokensfromyourbackend(chatid); return { idtoken, accesstoken }; }); if you already set getauthtoken on window\ kindlyoptions, you can call window\ kindlychat authenticate() with no argument to re run it deauthenticate to clear the user's authenticated identity during an active chat (for example when they log out), call window\ kindlychat deauthenticate(); this removes the stored token and forgets the getauthtoken function, so the chat continues unauthenticated until you authenticate again kindly clears the identity on its side too the user's details come off the chat, and the token stops being forwarded to your webhooks (optional) backchannel logout if your identity provider supports openid connect back channel logout, you can register kindly as a backchannel logout client so that ending a session at your idp (e g on your own logout page) deauthenticates the matching kindly chat, instead of waiting for the token to expire it's the server to server counterpart of deauthenticate() above the identity comes off the chat and the token stops reaching your webhooks, without your page having to be open register this endpoint with your idp as the backchannel logout uri, using your own workspace id in the path (the numeric id shown in your workspace's url in the kindly platform) post https //bot kindly ai/auth/backchannel logout/\<your workspace id> content type application/x www form urlencoded logout token=\<jwt> kindly verifies the logout token's signature, matches it to the chat session by sid or subject, and clears that session's identity so any later request needs to re authenticate whichever method you use, the provider id (iss) has to be your provider's real https issuer url that's the value kindly matches against the logout token's iss claim a provider id that isn't a url can't receive backchannel logout at all where the signature is verified from depends on your method jwks provider the jwks you already configured include a sid matching the one in your tokens to clear a single session; without one, kindly falls back to matching by subject kindly jwt your own public key sign the logout token with the private key you already use for chat jwts, with the iss you configured on the verifier, and include a sid matching the one in your chat jwts to clear a single session (see the token format) introspection provider your introspection endpoint isn't used here kindly instead verifies the signature via the discovery url or jwks uri you set on the introspection provider, or from \<iss>/ well known/ by default if provider id (iss) is already a discoverable https url unlike the flows above, this one is initiated by your identity provider, not the chat client sequencediagram participant u as user participant i as your identity provider participant k as kindly participant c as kindly chat client u >>i log out i >>k post /auth/backchannel logout/{workspaceid} w/ logout token k >>i verify logout token via jwks k >>k match chat session and clear its identity k >>c session identity cleared note over c not pushed to the client the next request finds itself unauthenticated your logout token needs iss (matching the method it belongs to), exp, iat, an events claim containing http //schemas openid net/event/backchannel logout, and sub, sid, or both, whichever your identity provider sends a nonce is not allowed it also needs an aud, which the spec requires anyway; on a jwks provider method that aud has to be one of your allowed id token audiences , when you've set that field the kindly jwt path is the exception those tokens carry no audience, so aud is optional there an introspection provider's logout token isn't checked against an audience list either way; there's no equivalent field for it today what gets cleared depends on which of the two your identity provider sends sid and sub clears the chats bound with that session id chats bound under one of the user's other session ids are never touched if no chat carries that sid at all (which happens when kindly never recorded one, for instance because your introspection response doesn't return sid), the logout falls back to this user's chats that have no recorded session id, so it still takes effect sid only the same, except there's no sub to fall back to if no chat carries that sid, nothing is cleared and you still get a 200 sub only clears every chat bound to that user for this issuer this is the all sessions logout kindly records a session id from the sid claim in your id token or kindly jwt, or from a sid in your introspection response supplying one is worth doing, since it scopes the logout precisely instead of relying on the fallback above, but a logout still works without it as long as your logout token carries sub kindly replies 200 when it accepted the token, including when no session matched that's a normal no op a 400 means the request carried no usable logout token a 403 means the token couldn't be verified, or that its issuer matches no method configured on the workspace; the two are deliberately indistinguishable, so the response can't be used to probe which issuers a workspace has requests are rate limited per workspace, generously enough for a mass logout, and a throttled request gets 429 with retry after session refresh for long lived sessions, the chat client re runs getauthtoken on your behalf shortly before the token expires, so users don't get silently logged out mid conversation you don't need to do anything for this beyond making sure getauthtoken returns a fresh token each time it's called the refresh fires roughly 30 seconds before the earliest expiry kindly knows about, which is the earlier of the expiresat you returned from getauthtoken, if any, and the expiry kindly determined when it validated the token the exp claim for a jwt, or the exp in your introspection response for an opaque token the second one is visible to you kindly returns it as expires at (unix seconds) in the response to the chat client's authentication request, and omits the field when it couldn't determine an expiry so if a refresh isn't firing when you expect, expires at tells you whether kindly read an expiry off your token at all if neither is available (an opaque token, no expiresat, and an introspection response without exp), there's no expiry to schedule against and no proactive refresh happens in that case set expiresat, or return exp from your introspection endpoint identify users in webhooks once the authentication setup is done, a token from your authentication endpoint is passed to the kindly api which can send the token to your webhook endpoints when a webhook contains a token it can be used to identify a user and provide support for a wide range of integration use cases you'll find the token in the standard authorization header, ie authorization bearer \<token> for dual token jwks provider setups, the forwarded token is your access token , not the id token remember, you can use the debug console to inspect webhook request and responses, including their headers webhook domains to prevent leaking authentication/tokens to unwanted third parties, the domains a token can be forwarded to must be known in advance kindly jwt encode chat webhook domains in the token itself, as described above jwks provider / introspection configure forward domains on the provider if the token kindly forwards is itself a jwt carrying chat webhook domains, the two lists are unioned, but note that's the access token in dual token setups, and an opaque token can't carry a list at all, so forward domains is the only allowlist there the token will not automatically be sent from kindly to webhooks outside these allowlists verify the token before trusting the authentication token we forward, you may want to validate in on your end kindly jwt verify using the public key you generated earlier, the same public key you entered on the kindly jwt verifier for your workspace, which kindly also uses to verify the jwt jwks provider, jwt access token verify against your own identity provider's jwks (single jwt / dual token access jwt) jwks provider, opaque access token (dual token setups) kindly stored this token unverified alongside the verified id token, so it arrives at your webhook unchecked validate it the way you already do elsewhere in your stack introspection, or your own session store introspection treat the opaque access token as you already do elsewhere in your stack after you have verified the token, you can extract information from it and be sure of its authenticity using more than one method you can configure several authentication methods on the same workspace (e g a jwks provider for your main app plus a kindly jwt verifier for a legacy integration) how a token is matched to a method depends on whether it's a jwt jwt tokens (kindly jwt, jwks provider) matched automatically by the iss claim in the token, which must equal the issuer url (iss) you set on the method nothing extra to select at runtime opaque tokens (introspection) the token has no issuer claim, so there's nothing to match on always return an issuer from getauthtoken matching the method's provider id (iss)

