My Profile Photo

Jenny from the blog

Software Engineer | MSc in Information Security | Co-founder @Authentick

Insecure Zendesk SSO implementation by generating JWT client-side

Belle of the Ball

Two months ago I submitted a security bug report to Trint Ltd. on HackerOne. It got disclosed today and managed to get ranked on top of hacktivity feed ;)

Belle o the Ball

Below you can find the report originally submitted here.

Summary implements SSO to Zendesk, it does this by using JWT as described at

This functionality has not been implemented securely because the JWT generation happens in the client-side. This is done by the Zendesk secret being hardcoded in the JavaScript code. The secret is used to create JSON Web Tokens and then you can use the generated token to impersonate any customer in Zendesk. (therefore potentially getting access to their support tickets)

Whilst is marked as out of scope for the program, the described vulnerability isn’t caused by Zendesk. The vulnerable component is in


The JavaScript source map files are available next to the minified production files. This significantly makes analysing this issue easier.

  1. JavaScript file:
  2. Sourcemap file:

Looking at some of the UI views, I stumbled upon static/js/modules/auth/pages/ZendeskLoadingPage.jsz I’ve attached a stripped version which shows the JWT generation:

import { ZENDESK_DOMAIN } from 'modules/core/constants/index';

const { REACT_APP_ZENDESK_SECRET } = process.env;

function RedirectToZendesk(props) {
  const { user, history } = props;

  function generateZendeskTokenAndRedirect() {
    const TIME_NOW_OBJECT = moment(;
    try {
      const payload = {
        iat: TIME_NOW_OBJECT.unix(),
        jti: uuid.v4(),
        name: `${user.profile.firstName} ${user.profile.lastName}`,
        email: user.username,

      // encode zendesk token
      const zendeskToken = jwt.sign(payload, REACT_APP_ZENDESK_SECRET);
      window.location = `${ZENDESK_DOMAIN}/access/jwt?jwt=${zendeskToken}`;
    } catch (err) {

    () => {

  return <Loader />;

export default ZendeskLoadingPage;

Searching for REACT_APP_ZENDESK_SECRET in the sourcemap will show the JWT secret:

var REACT_APP_ZENDESK_SECRET = "oq1HJ4jXo99Wt41bwvLh9BXBVdgpi52CjkXbThow7UhWQGtJ"; ` Generating the JWT on the client-side like this allows anyone to mint an arbitrary JWT. It would probably be better to generate this on the server-side.

Reproduction steps

As logged-in user press “Support” on Intercept the traffic and see the call to[JWT_TOKEN] Logout of Zendesk Put the JWT token from above URI into and decode it. Example:

  "iat": 1562709659,
  "jti": "21d02987-e7ab-4490-97d7-76a0f32a95c8",
  "name": "Test Test",
  "email": "[email protected]"

Now we can continue with tampering the JWT Change IAT to the current Unix timestamp Change JTI to a random UUID v4 Change email to the victim email address Insert oq1HJ4jXo99Wt41bwvLh9BXBVdgpi52CjkXbThow7UhWQGtJ as HMAC secret. Use the resulting JWT in a call to[JWT_TOKEN]. You will be logged in as the victim.


Access to the Zendesk account of Trint customers. This includes potentially the support history of said user.

I haven’t verified whether the same SSO flow can also be used against Zendesk administrators. If so, the risk would be higher.