Single sign-on

How JWT single sign-on works

Export
Download Markdown Use with AI

JWT single sign-on lets people who are signed in to your product read your private help center without a second login. Your server vouches for each reader with a short-lived token, signed with a secret that only you and HelpCenter.io know, and HelpCenter.io signs the reader in.

This page explains the three ways a token reaches HelpCenter.io, what a valid token does, and what single sign-on doesn't do. To write the endpoint, see Build your Login URL. For every claim and check, see JWT claims and validation rules.

Plans: Setting up single sign-on needs the Catalyst plan. A setup saved earlier keeps working, and stays editable, on other plans. Who sets it up: Owners and Admins, in the dashboard. Your developers build the Login URL.

The pieces

Piece

What it is

Login URL

An endpoint in your app, for example https://app.example.com/auth/helpcenter. HelpCenter.io sends readers there to sign in. You enter its address in your help center's settings.

Shared secret

A string of at least 64 characters, generated in your settings or chosen by you. Your server signs tokens with it using HS256, and HelpCenter.io checks every signature against it. It stays on your server.

Token

A JSON Web Token (JWT) that names the reader. It needs jti, iat, exp, email and name, and should carry external_id.

/sso/jwt

The address on your help center where your Login URL sends the reader back with the token: https://help.example.com/sso/jwt?jwt=<token>.

Three ways a token signs a reader in

All three use the same shared secret and the same claims. They differ in how the token travels, what jti holds, and what happens after sign-in.

Where

How the token arrives

jti

After sign-in

Your help center

HelpCenter.io sends the reader to your Login URL, which sends them back to /sso/jwt?jwt=<token>.

The key HelpCenter.io sent to your Login URL

A session on the help center

Your help center in an iframe

Your page loads a help center address with ?jwt=<token> added.

A new value for every token

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

The widget

Your page passes a token in window.hcOptions.jwt, and each widget request carries one in Authorization: Bearer <token>.

A new value for every token

No session: every request carries its own token

Sign-in on your help center

This is what happens when someone opens your help center directly. It applies when the help center is set to Private.

  1. A visitor opens a page of your help center, for example https://help.example.com/content/billing?ref=app.

  2. They aren't signed in, so HelpCenter.io remembers the page, query string included, and creates a one-time key. It sends the visitor to your Login URL with two query parameters: subdomain, your help center's subdomain (the part before .helpcenter.io, even when you use a custom domain), and key.

  3. Your endpoint finds the user in your own session. If nobody is signed in, it shows your login page first, then comes back to the same URL with the same key.

  4. It signs a token with HS256 and the shared secret, with jti set to the key, and redirects to https://help.example.com/sso/jwt?jwt=<token>.

  5. HelpCenter.io checks the signature and the claims, and checks that the key is an unused key of this help center. It finds or creates the reader, signs them in, and marks the key as used.

  6. The reader lands on the page from step 1.

The first redirect, as the visitor's browser sees it:

GET /content/billing?ref=app HTTP/1.1
Host: help.example.com

HTTP/1.1 302 Found
Location: https://app.example.com/auth/helpcenter?subdomain=acme&key=kT9wQ48213pZ2mV

And the last one, once your endpoint has sent the reader back with a valid token. The response also sets the session cookie.

GET /sso/jwt?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJrVDl3UTQ4MjEzcFoybVYi... HTTP/1.1
Host: help.example.com

HTTP/1.1 302 Found
Location: https://help.example.com/content/billing?ref=app

These are browser redirects: HelpCenter.io never calls your Login URL from its servers. Your readers' browsers follow every step, so the endpoint only has to be reachable by them.

Sign-in in an iframe

When you show your help center inside your app, you can sign the reader in with the first page you load.

  1. Your server signs a token for the user, with a new random jti.

  2. Your page loads any help center address with the token added: <iframe src="https://help.example.com/content/billing?jwt=<token>">.

  3. HelpCenter.io checks the token, signs the reader in with a session, and the page renders.

Three things to know about this flow:

  • The session lasts beyond the first page only on the same site. Browsers keep the help center's session cookie in the frame when your page and the help center share a site, for example help.example.com (your custom domain) inside app.example.com. With acme.helpcenter.io inside app.example.com, the first page is signed in and the next page sends the frame to your Login URL. If your Login URL signs readers straight back in, the frame then goes around in a loop until the browser gives up.

  • A token that doesn't work is ignored. An invalid, expired or already used token, or any token for a visitor who is already signed in, is skipped without an error. The visitor carries on without it, and a private help center sends them to your Login URL.

  • Only exp limits the token. The Token TTL setting doesn't apply here. Keep exp short, because the token stays in the frame's address.

The embed itself (the script, the frame's height, allowed origins) is in Embed your help center in your app.

Sign-in in the widget

  1. Your page gets a token for the signed-in user from your server, with a new random jti, and passes it in window.hcOptions.jwt, together with an onAuthExpired function that returns a fresh token.

  2. The widget hands the first token to its frame in the URL fragment (#jwt=), which browsers don't send to servers.

  3. Every widget request carries a token in Authorization: Bearer <token>. After the first request, the widget asks onAuthExpired for a new one.

  4. HelpCenter.io checks each token, including its age against Token TTL, and answers as that reader. No cookie is involved.

  5. Without a valid token, the widget's requests to a private help center answer 403 Forbidden with the code SITE_AUTH_REQUIRED.

The complete setup, with code, is in Sign readers into the widget with a JWT.

What a valid token does

  • Finds or creates the reader. HelpCenter.io looks the reader up by email or external_id and creates one on the first sign-in. The name, email and profile claims are saved then, and later tokens don't change them. See JWT claims and validation rules.

  • Lets them read. A reader can open a private help center and read everything that's open to all readers. Categories for team members only, and categories or articles shared with selected team members, stay hidden.

  • Keeps them signed in. On the help center and in an iframe, the reader gets a session that lasts until 24 hours pass without a visit. Each visit starts the 24 hours again. The widget keeps no session.

  • Never opens the dashboard. A role claim is ignored, and every reader has the same access. Your team keeps signing in with their HelpCenter.io accounts.

What single sign-on doesn't do

  • No roles, groups or per-reader access. Every SSO reader sees the same content. Claims such as role, roles or groups are ignored, and private categories are shared with team members, not with readers. Audiences that need different content need different help centers. See Can different readers get different access?

  • No sign-out. The help center has no sign-out link, and the Logout URL setting is saved but not used. A reader stays signed in until 24 hours pass without a visit, even after you sign them out of your app or remove them from it.

  • No reader list. Readers don't appear under Users, and you can't remove or suspend them.

  • No switching off in the dashboard. Once you save the settings, switching off Enable Single Sign-On only hides them, and the saved setup keeps working. To stop sending readers to your Login URL, change the help center's visibility, or contact support to remove the setup.

  • One setup per help center. One Login URL, one shared secret, and one optional Issuer and Audience. See Can I have more than one SSO configuration on a help center?

  • HS256 only. RS256 tokens, public keys, JWKS and OpenID Connect ID tokens aren't accepted. SAML 2.0 and OpenID Connect show as Coming soon in the dashboard. Reader sign-in is also unrelated to the tokens of OAuth 2.1 for apps and AI clients.

  • The Login URL is for private help centers. A Public help center never sends visitors to it, although a token in an iframe or the widget still signs them in. A Password help center shows its password page instead. Visitors outside your IP allow-list land on the HelpCenter.io sign-in page.

Set it up

The settings are in your dashboard under Settings → Single sign-on. The steps are in Sign readers in with your own login (JWT SSO).

Screen recording. Under Single sign-on, switch on Enable Single Sign-On. Enter your Login URL and generate a Shared secret. Click Save JWT settings.
Generate creates a 64-character secret for you.

What each setting means for your code:

Setting

What it means for your code

Login URL

Required. The address HelpCenter.io sends readers to. HelpCenter.io adds ?subdomain=…&key=… to it, so it must not contain a ? or # of its own.

Shared secret

Required, at least 64 characters. Your server signs with this exact string. A generated secret looks like base64, but don't decode it.

Issuer (iss claim)

Optional. When set, every token needs an iss claim with exactly this value.

Audience (aud claim)

Optional. When set, every token needs an aud claim with exactly this value, as one string.

Token TTL (seconds)

Widget tokens only: the oldest token the widget accepts, counted from iat, with 30 seconds of tolerance. From 30 to 86,400; 300 is recommended.

Logout URL

Saved but not used.

Security notes

  • Keep the secret on your server. Anyone who has it can sign in as any reader, because the token's email and external_id are the reader's identity. Never put it in browser code.

  • Write your help center's address into your code. Never build the redirect from the subdomain parameter, or a crafted link could send a valid token to another site.

  • Keep exp short. 300 seconds leaves plenty of time. Tokens for your help center and for iframes travel in URLs, which can end up in browser history and server logs. The widget passes its first token in the URL fragment, which browsers don't send to servers.

  • Use a different secret for each help center. A token is accepted by every help center that uses the secret it was signed with, unless their Audience settings tell them apart.

  • Rotate with care. A help center has one secret at a time. A new secret takes effect as soon as you save it: tokens signed with the old one fail from then on, and readers who are already signed in stay signed in. See Can I rotate the SSO shared secret without downtime?

  • Keep your clock in sync. HelpCenter.io allows 30 seconds of difference on iat, nbf and exp. Use NTP on the servers that sign tokens.

Was this article helpful?