Single sign-on

JWT claims and validation rules

Export
Download Markdown Use with AI

Every token that signs a reader in to your help center follows one contract, whether it comes back from your Login URL to /sso/jwt, arrives in an iframe's ?jwt=, or rides in the widget's Authorization header. This page lists every claim and every check HelpCenter.io applies.

A token HelpCenter.io accepts, decoded. The header:

{
  "alg": "HS256",
  "typ": "JWT"
}

The payload, for sign-in through your Login URL:

{
  "jti": "kT9wQ48213pZ2mV",
  "iat": 1767225600,
  "exp": 1767225900,
  "email": "jane.doe@example.com",
  "name": "Jane Doe",
  "external_id": "4815"
}

Signing

  • Algorithm: HS256 (HMAC with SHA-256) only. A token signed with HS384, HS512 or RS256, or with alg set to none, is rejected even when its signature is correct.

  • Key: the Shared secret from your settings, exactly as shown, used as a string. It has at least 64 characters, and a generated one looks like base64, but it isn't decoded: a token signed with the decoded bytes is rejected.

  • Header: alg is required. typ is optional, and kid is ignored.

Claims

Claim

Type

Rule

What HelpCenter.io does with it

jti

string

Required. For sign-in through your Login URL, the key HelpCenter.io sent. For the iframe and the widget, a new value for every token.

Makes each token single use (see below).

iat

number, Unix seconds

Required. At most 30 seconds in the future. For the widget, also at most Token TTL plus 30 seconds in the past.

Checked only.

exp

number, Unix seconds

Required. The token works until 30 seconds after exp. There's no upper limit, so keep it short: iat plus 300.

Checked only.

nbf

number, Unix seconds

Optional. At most 30 seconds in the future.

Checked only.

email

string

Required, not empty. Its format isn't checked. Up to 240 characters.

Identifies the reader, together with external_id. Matched without regard to letter case. Saved when the reader is created.

name

string

Required, not empty.

Split at the first space into a first and last name, and shown on the reader's comments. Saved when the reader is created.

external_id

string

Optional, recommended: your own stable user ID. A number also works. Up to 140 characters.

Identifies the reader, together with email. Saved only when the reader is created.

iss

string

Required when your settings have an Issuer, and then equal to it, letter case included. Ignored otherwise.

Checked only.

aud

string

Required when your settings have an Audience, and then equal to it. Always one string, never a list.

Checked only.

lang

string

Optional. Up to 10 characters.

Saved when the reader is created (otherwise your help center's default language is saved). The help center doesn't use it.

avatar_url

string

Optional. Up to 255 characters.

Saved when the reader is created. The help center doesn't show it.

custom_fields

object

Optional.

Saved when the reader is created. The help center doesn't use it.

role

any

Ignored.

Every SSO visitor is a reader, whatever the claim says.

sub, groups, roles, others

any

Ignored. sub doesn't replace email.

Nothing.

Numbers sent as strings, such as "iat": "1767225600", are accepted, and so are fractions of a second.

Time checks

All times are Unix timestamps in seconds. HelpCenter.io allows 30 seconds of difference between your clock and its own:

Check

Accepted

Rejected

iat ahead of HelpCenter.io's clock

Up to 30 seconds

More than 30 seconds

nbf ahead of HelpCenter.io's clock

Up to 30 seconds

More than 30 seconds

exp behind HelpCenter.io's clock

Less than 30 seconds

30 seconds or more

Widget only: seconds since iat

Up to Token TTL plus 30

More than Token TTL plus 30

For example, a token whose exp passed 20 seconds ago still signs a reader in, and one whose exp passed 40 seconds ago doesn't. With Token TTL at 300, the widget accepts a token issued 320 seconds ago and rejects one issued 340 seconds ago.

Milliseconds are the most common mistake: Date.now() in JavaScript returns milliseconds, which puts iat thousands of years in the future, and the token is rejected. Use Math.floor(Date.now() / 1000).

Single use

  • Sign-in through your Login URL: jti must be the key HelpCenter.io sent to your Login URL. Each key works for one successful sign-in, on the help center that issued it. A token HelpCenter.io rejects doesn't use up the key.

  • Iframe and widget: jti can be any value you haven't used before on this help center, such as a UUID. Use each jti once. When HelpCenter.io sees a jti it already accepted, an iframe page with a reused token loads as if it had none, and on a private help center the widget answers 403 Forbidden. The iframe and the widget share this record, so a token used in an iframe can't be reused in the widget.

How long tokens and sessions last

Flow

The token is accepted while

After sign-in

Login URL (/sso/jwt)

exp hasn't passed by 30 seconds or more

A session that lasts until 24 hours pass without a visit

Iframe (?jwt=)

exp hasn't passed by 30 seconds or more

The same session, kept only when your page and the help center are on the same site

Widget (Authorization: Bearer)

exp hasn't passed by 30 seconds or more, and the token is no older than Token TTL plus 30 seconds

Nothing: each request carries its own token

Token TTL is set in your settings, from 30 to 86,400 seconds, and 300 is recommended. It applies to widget tokens only. For how long a token should live, see How long should my SSO tokens live?

What the reader record keeps

The first valid token for a person creates their reader record. After that:

  • Details are written once. name, email, external_id, lang, avatar_url and custom_fields keep the values from the first sign-in. For example, a reader who first signed in as "Mary Jane Watson" (first name Mary, last name Jane Watson) keeps that name when a later token says "Mary Watson-Parker", and a new avatar_url, lang or custom_fields in that token is ignored.

  • A known external_id wins over a new email. A token with a new email and a known external_id signs in the existing reader, and the saved email stays the first one.

  • external_id is saved only at creation. A reader created without one stays matched by email only. If their email changes later, the next token creates a second reader, even with an external_id. Send external_id from the first sign-in. See What if a reader's email changes on our side?

  • Letter case doesn't matter. Jane.Doe@Example.com and jane.doe@example.com sign in the same reader.

Tokens that cause a server error

A few tokens fail with a server error (HTTP 500) instead of being rejected, in all three flows. Readers see HelpCenter.io's "Whoops, something crashed." page, and widget requests fail:

  • aud as a list, such as ["helpcenter-prod", "other"], even when no Audience is set. Some libraries produce a list when you give them several audiences.

  • email, name or another string claim sent as an object or a list.

  • On a reader's first sign-in: external_id, lang or avatar_url over the limits in the table above.

An email over its limit doesn't cause an error: through your Login URL the reader lands on the HelpCenter.io sign-in page, and in an iframe or the widget the token is refused like an invalid one. The token checker in Troubleshoot single sign-on catches all of these.

What HelpCenter.io answers

Outcome

Login URL (/sso/jwt)

Iframe (?jwt=)

Widget

Token accepted

302 Found to the page first asked for, signed in

The page, signed in

The data, as that reader

Token rejected (signature, algorithm, missing claim, time, Issuer or Audience)

401 Unauthorized with the text "Invalid SSO token."

The token is ignored and the visitor stays signed out

403 Forbidden, code SITE_AUTH_REQUIRED, on a private help center

jti isn't an unused key of this help center

302 Found to the HelpCenter.io sign-in page

Not checked

Not checked

No token

400 Bad Request with the text "Missing jwt parameter."

A private help center sends the visitor to your Login URL

403 Forbidden, code SITE_AUTH_REQUIRED, on a private help center

HelpCenter.io doesn't say why it rejected a token. Troubleshoot single sign-on has a checker that applies the same rules and names the one a token breaks.

Was this article helpful?