Anatomy of a JWT request Follow

At the core of single sign-on authentication is a technology called JSON Web Token (JWT) that allows Zendesk to trust the login requests it gets from your systems. See Setting up single sign-on with JWT (JSON Web Token) for details

This is what a JSON web token authentication request looks like:

Putting this in the URL bar in a browser logged me into a Zendesk instance.

It's that simple. Nothing else required. No servers speaking to each other out of sight. It all happens in a URL.

A remote authentication script simply has to build it (well, something like it) and point the user there.

Let's break the token down.

The first bit is a URL endpoint where remote authentication requests should be directed. That's followed by a question mark (?), which indicates that you are adding parameters. Parameters pass some information to the script at the destination endpoint ( The script has to be designed to accept these parameters, which is why you need to send it to /access/jwt and not just some random address.

After the question mark, 'jwt' is the name of the parameter, and '=' indicates that the string that follows is the value of the parameter.

The rest is data. If you look closely, you'll see it's divided into chunks by periods (full stops). We'll look at each chunk in turn.

Chunk 1: JWT Header

The first chunk is the JWT header. It indicates that it's a JWT request, and indicates the type of hashing algorithm used. (More on that later.)


The big reveal about this (and the rest of the data) is that it is base64 encoded. This is not actually encryption so it can be easily decoded by tools like the following:

This is what that string looks like decoded:


You can see that it takes a JSON structure and has two key-value pairs that effectively mean type: JWT and Algorithm: HMAC SHA 256. SHA 256 is a 256-bit encryption algorithm designed by the U.S. National Security Agency. It's used to generate the third chunk -- the signature -- which we'll get to in a bit.

Chunk 2: JWT Claims Set/Payload

The second chunk is considerably longer because it contains the payload. It's known as the 'JWT Claims Set'.


This contains a timestamp, a random value, a user name, email address, external ID, and some tags. There are even more options available. Again, just base64 decode it to make sense of the payload. I'll split the lines to make it easier to digest.

"name":"Test User",



The first key is iat, which stands for issued at. This is a timestamp formatted as whole seconds since January 01, 1970, which is a standard UNIX representation of time. The timestamp must be an integer (no decimals) and it must be in UTC. It also must be within 3 minutes of the current time when the Zendesk server receives it. This puts a self-destruct mechanism in each remote authentication request, preventing any single request from being used more than 3 minutes after it was generated.


The second key, jti, stands for JSON Token ID. It's really just a random string. It has to be sufficiently long and random so that it's very unlikely to be used again on this account. If, by accident or chance, it is re-used for another authentication request, the request will fail. What this amounts to is a disposable key. By including a (mandatorily) random value like this, we ensure that no two authentication requests are ever the same. This prevents reuse of a valid request URL. For example, imagine that someone succeeded in installing malware on your computer or network and has started logging your traffic. They can see every URL you hit. If they see and grab the URL you were issued to log in to Zendesk, they could use it themselves to log in as you within 3 minutes without this single-use key.


Next is the user's full name with spaces included. Whatever Zendesk receives here is set as the user name, even if they had a different name set before.


After that is the user's email. This is used as the unique identifier for a user unless an external ID is received*. This means that if we get an email and an external ID, we try to match the ID first. If we do, we update that user with the specified email address.

*Note: If the option 'Allow update of external IDs' is enabled in Zendesk, we'll continue to key off the email even if an external ID is received. If the email and external ID differ, we'll change the ID. 


External ID

External ID is an optional ID you can use to identify users instead of using their email address (noted above).


You can also pass an organization value to add a user to an organization. The named organization must already exist and the name must match exactly. Otherwise no action is taken.


The tags key allows you to set tags on the user being logged in. The key replaces any existing tags on the user with the tags you specify, so use it carefully. Passing a blank tags parameter removes all tags from a user.

Remote Photo URL

You can also pass a value for remote_photo_url, which accepts a public URL containing a photo and sets this photo as the user's profile picture.

Locale (Language) 

You can pass a value for locale_id to set or update the authenticated user's language in Zendesk. The value must be a number that matches a currently activated locale in your Zendesk. You can find the locales by using the locales.json endpoint of our API:

User Fields (not shown in example)

This must be a JSON object that includes key-value pairs for each field key and value. The field key can be found or defined in the User Fields interface. Note that only custom user fields can be passed.


"user_fields": {"checked": false,"date_joined": "2013-08-14T00:00:00+00:00","region": "EMEA","text_field": null}

Checkboxes use boolean values, date codes follow the example above, and drop-downs accept the name of the option. Text fields accept strings.

Phone Number (not shown in example)

This accepts a string for a phone number identity.

Chunk 3: JWS Signature

The last chunk of the request is the encrypted part. You don't need to worry too much about this, but basically it takes all the information above (iat, jti, name, email, etc) along with the shared secret and generates an encrypted string out of all of it. Then, per JWT standards, a chunk of that encrypted string is taken (known as a checksum), and that is the JWS signature.

So how is this secure?

To generate a valid encrypted string, you must know the shared secret.

Encryption is designed so that you cannot work backwards from the encrypted string to the data and the key. Even if you have the data contents, it's practically impossible to deduce the key.

But since you have the key, and we have the key, and you send us the same data unencrypted as you do encrypted, we can build the signature ourselves and check that it matches what you sent.

It also insures nobody can tamper with the data in transit.

This is how you build it in pseudo-code:

URLBase64Encode( header_json ).URLBase64Encode( payload_json )

When generating the HMAC-SHA256 of the encoded header and payload, you include the shared secret.

This is the result:


Have more questions? Submit a request


  • 0

    "*Note: If the option 'Allow update of external IDs' is enabled in Zendesk, we'll continue to key off the email even if an external ID is received. If the email and external ID differ, we'll change the ID. "

    Very well explained! Understanding this is crucial for solving many JWT SSO issues.

    Edited by Matt Hoffman
  • 0

    Great piece, very informative.

    under what header does the JWT Header goes in the initial POST request? 

  • 1

    Hi Igor!

    The JWT header is used in your external script to form the JWT payload. It is the first chunk explained in this article. We have examples of these scripts (in various languages) here if you would like to check the implementations out. You might also find this article handy.

    If you need further help, let us know and we'll be happy to explore your questions in a ticket. Thanks, Igor!

Please sign in to leave a comment.

Powered by Zendesk