Authentication
setup to use authenticated chat sessions, pass an authtokenprovider to the start method kindlyauthtokenprovider is a fun interface with a single suspend function, so you can implement it with a lambda import no kindly chatsdk chat kindlyauthtokenprovider kindlysdk start( application = this, botkey = "bot key", languagecode = "en", market = "your market", authtokenprovider = kindlyauthtokenprovider { request > // suspend code â e g call your backend to mint a jwt for request chatid fetchjwtfrombackend(chatid = request chatid) } ) the sdk calls fetchtoken(request) from its own coroutine whenever it needs a token â you don't need to manage a coroutinescope , deferred , or threading yourself return the jwt string, or throw an exception to signal failure authtokenrequest the request tells you which chat session the token is for and why the sdk is asking property meaning chatid the current chat session id pass it to your backend so the token is scoped to this session reason initial connect â the first token for a new session expired â the cached token is expired (or about to expire); mint a fresh one, never return a cached token behavior token fetches are capped by a 15 second sdk side timeout if your provider takes longer, the fetch is abandoned and the chat continues unauthenticated tokens that arrive already expired are discarded, so make sure your backend always mints a fresh token â especially when reason == expired jwts without an exp claim are treated as valid and are never proactively refreshed migrating from authtokencallback (deprecated) earlier sdk versions used a deferred based callback kindlysdk start( application = this, botkey = "bot key", languagecode = "en", market = "your market", authtokencallback = { chatid > val deferredtoken = completabledeferred\<string>() // async code, then on completion // deferredtoken complete(token) deferredtoken } ) this continues to work â existing integrations do not break â but it is deprecated to migrate, replace the callback with a provider and drop the deferred plumbing // before authtokencallback = { chatid > coroutinescope(dispatchers io) async { fetchjwtfrombackend(chatid) } } // after authtokenprovider = kindlyauthtokenprovider { request > fetchjwtfrombackend(request chatid) } identity providers that issue more than one token the kindly platform verifies an authenticated chat in one of three ways a jwt your own backend signs, a token from your identity provider (an id token, an access token, or the pair), or an opaque bearer token validated by introspection the first is the single string case above for the other two, use authcredentialsprovider import no kindly chatsdk chat kindlyauthcredentials import no kindly chatsdk chat kindlyauthcredentialsprovider kindlysdk start( application = this, botkey = "bot key", market = "your market", authcredentialsprovider = kindlyauthcredentialsprovider { request > val tokens = fetchtokens(request chatid) kindlyauthcredentials( idtoken = tokens idtoken, // optional, the preferred bearer accesstoken = tokens accesstoken, // optional, companion or bearer issuer = "https //idp example com/", // optional expiresat = tokens expiresatunixseconds, // optional, unix seconds ) }, ) field required when you need it idtoken no the oidc id token, when your provider issues one the preferred bearer accesstoken no the access token, when your provider issues one companion next to an id token, bearer on its own issuer no bots with more than one provider configured, and any opaque token sent as x auth issuer expiresat no opaque tokens, where there is no exp claim for the sdk to read unix seconds every field is optional what the sdk requires is that at least one of idtoken / accesstoken is set, because the backend's auth route mandates an authorization header and nothing else credentials carrying neither cannot authenticate, and the chat stays anonymous what goes on the wire three derived properties spell out the rule, so you never have to re derive it property value header bearertoken idtoken ? accesstoken , first non empty, else null authorization bearer hastoken bearertoken != null the validity check, never idtoken != null none companionaccesstoken the access token, but only when an id token is also set and differs from it x access token x access token and x auth issuer are read by the backend on auth/chat , message and trigger only the companion is deliberately null when the access token is itself the bearer, and when it merely duplicates the id token that second case has teeth the backend verifies the companion against the id token's at hash and rejects a mismatched pair, so sending a duplicate would fail an auth that would otherwise have succeeded the sdk drops it for you a provider that issues only an access token normal and supported, not a workaround leave idtoken unset and the access token becomes the bearer kindlyauthcredentials( accesstoken = tokens accesstoken, issuer = "https //idp example com/", expiresat = tokens expiresatunixseconds, ) a single jwt integration never needs this type when more than one auth parameter is passed to start( ) , precedence is authcredentialsprovider > authtokenprovider > authtokencallback (deprecated) token refresh the sdk refreshes credentials on its own, 30 seconds before they expire expiry can come from three places, and whichever is earliest wins expires at returned by the backend when it bound the credentials expiresat you supplied on kindlyauthcredentials the exp claim of the bearer, when it is a jwt the bearer, not the id token specifically, so a credential carrying only an access token still refreshes on that token's own claim if none of the three is known the credentials are treated as non expiring and never proactively refreshed that is correct for a jwt with no exp claim, but it is a trap for an opaque token supply expiresat or the sdk will keep using it until the backend rejects it a token the sdk cannot parse means expiry unknown, never expired opaque bearers are a supported credential and are not discarded on arrival for being undecodable; the sdk just has no way to guess when they die, which is what expiresat is for re authenticating mid session expiry is handled for you these two are for when the identity itself changes, which the sdk cannot detect kindlysdk authenticate() // user signed in, or switched account kindlysdk deauthenticate() // user signed out authenticate() asks your provider for fresh credentials and re binds them to the chat that is already open, keeping the conversation and its history deauthenticate() unbinds the identity on the backend and clears the stored credentials the chat carries on anonymously with its history intact use endchat() instead when the conversation itself should end it is safe to call when the chat is already anonymous both return a deferred\<unit> ; awaiting is optional, and worth doing when you need to know the backend agreed deauthenticate() is idempotent, so calling it on an already anonymous chat completes immediately