chatwoot
chatwoot copied to clipboard
Dashboard App to allow authenticated requests
Is your feature or enhancement related to a problem? Please describe.
this is an enhancement.
recently we integrated our first dashboard, and we noticed there was no authentication provision , just the name and URL addition.
the details that are being fetched by the API are also public
Describe the solution you'd like
there should be atleast be an optinal provision to pass along a key/secret value when configurating a new dashboard app.
So people who do not want to expose publicly accessibly data can utilize this feature as well.
Describe alternatives you've considered
No response
Additional context
No response
@intrealm Check out this thread https://github.com/orgs/chatwoot/discussions/5878
thats pretty much the ask. https://github.com/orgs/chatwoot/discussions/5878#discussioncomment-4161227
There are two ways to securely enable Dashboard Apps. I will describe both, but option 1 will be much better, and both option can coexist. The solution presented in thread 5878 is not ideal since agents will need to authenticate twice and possible many times if different dashboard apps are hosted in different places... and basic authentication is subject to man the middle attacks. And other suggestions of adding random information or keys in the querystring or URL is insecure, since agents could capture the URL and share it with others from outside the organization, and the links will still work.
So my proposal will achieve the following, only agents that successfully authenticated in ChatWoot will be able to use dashboard apps without a second authentication, and the dashboard app will be able to know the identity of the agent, and in with the same approach, even the role of the agent could be added to the solution.
- Asymmetrically Signed Token
ChatWoot already has an authentication token. This token contains information about the agent that has signed in:
{ "access-token": "76Uld00tyTTU6-XXXXXXXX", "token-type": "Bearer", "client": "xxxxxxx_xxxxxx_x_xxxxx", "expiry": "1718456983", "uid": "[email protected]" }
And authentication token is already being used by ChatWoot to make API calls, I have seen it being sent in fetch request in the Authentication header as a Bearer token, base64 encoded.
The information inside is also avaiable in the as part of the "cw_d_session_info" cookie and also is sent in their own headers.
So main problem here is that, how can an external server trust this information?
So the solution is already invented, the information needs to be signed. If it is signed with a private key, anyone with access to the public key could verify the information. This means that Dashboard Apps could be developed in any technology and hosted anywhere, and they would be able to validate that whoever presents the token and claims to have the "uid" identity and expiring by the "expiry"value (one hour or less) is indeed who they claim to be.
I think JWT which are basically what is being already used as the Bearer tokens plus the signature is the best approach. These JWT could be stored as cookies or on the local storage. Since iframes are required, cookies seems a better alternative. This JWT could be accessed by the Dashboard App and used to fetch its own information in a secure way. This may require the use of a reverse proxy so the iframe can have the same top level domain as chatwoot. If this is not possible, then the JWT could be passed in querystring, which is not ideal but totally possible. These tokens expire after an hour so there is not risk.
I like this approach since the generation of the Asymmetrically Signed Token can be part of the same process that current generates the access-token, which I assume has all the logic to refresh them after expiration is in place and this will simplify a lot the functionality.
This approach does not require Dashboard Apps or any API used by the Dashboards Apps to validate the session with ChatWoot servers, nor access to Redis or anything. The only validation required is the signature validation.
We can go a bit further and follow Open ID Connect protocol and include an ISS claim that will help on obtaining the public keys needed to calculate the hash.
- Proxied Requests with Placeholder Variables
This approach is different. I have seen this approach in ZenDesk who offers a proxy that can receive and forward requests to external servers. This proxy will of course help with CORS issues but it also helps with security since the browser can make requests without knowing API Keys and Secrets to the external servers, and these values will be injected by the proxy before forwarding the request to the external servers and without revealing them to the browser. This works by setting placeholders in the request that the proxy can replace with environment values and even agent session values and conversation values.
I find this approach more complex to develop and to host than implementing a JWT signed with an asymmetric key.
A third alternative will be to sign the conversation object that is available to the Dashboard app through appContext conversation payload as explained here:
https://www.chatwoot.com/hc/user-guide/articles/1677691702-how-to-use-dashboard-apps?ref=www-internal-blog.chatwoot.com
The object contains a a lot of useful information include the currentAgent object:
{ "email": "string", "id": "integer", "name": "string" }
And the contact object which will be super useful for any Dashaboard App.
But again, how does the external hosted App can trust this information? Is there a way already implemented? If not, signing the payload will do the trick. So in summary:
- Sign conversation payload (appContext)
By signing the conversation, either with a private key or even a symmetric key that can be configured when creating the Dashboard App, the Dashboard App could retrieve the conversation history including contact and currentAgent objects to validate that the information is correct and it is ok to retrieve contextual information that may be sensible, like addresses, amounts and names of family members.
{ "event": "appContext", "data": { "conversation": { // <...Conversation Attributes> }, "contact": { // <...Contact Attributes> }, "currentAgent": { // <...Current agent Attributes> } } }