Ferndesk
Authentication

JWT Authentication

Identify users in your help widget without requiring a separate login. When users are logged into your app, you can pass their identity to Ferndesk using a JWT token signed by your backend.

You'll need your JWT secret from Help Center > Access Control and the Ferndesk SDK installed.

JWT identify is available on all plans and works in both Open and Locked access modes. You do not need to lock your help center or upgrade to generate a secret.

How It Works

Three-step flow:

  1. Your frontend detects a logged-in user

  2. Your backend generates a signed JWT with user details

  3. Your frontend calls Ferndesk('identify', { jwt })

The help center and widget now knows who the user is for authentication, personalization and analytics.

The identify method only works from the same domain as your help center or a 1-level subdomain. If your help center is at help.example.com, you can identify from app.example.com but not otherdomain.com.

JWT Secret

Before you can sign JWTs, you need a signing secret from your Ferndesk dashboard:

  1. Go to Help Center > Access Control.

  2. In the User identification section, expand the JWT identify row.

  3. Click Generate Secret.

Generating a secret automatically enables JWT authentication for your help center. The secret is shown only once. Copy it immediately and store it securely in your backend environment variables. You won't be able to view it again.

If you need to rotate the secret, click Regenerate. This replaces the existing secret and invalidates all tokens signed with the old one. Update your backend with the new secret before users are affected.

You can only have one JWT secret per help center. Generate Secret is only available when no secret exists. Regenerate is available when a secret already exists.

Browser sign-in for MCP and AI clients

JWT identify works silently in your product. AI clients sign users in through a browser instead.

In JWT identify under Browser sign-in (MCP and AI clients):

  1. Enter your SSO login page URL. Ferndesk redirects users here with a return_to parameter. After sign-in, redirect to return_to with a jwt query parameter.

  2. Optionally turn on Allow email sign-in for AI clients so existing users can prove email ownership when connecting an AI client. Leave this off if user attributes gate sensitive content.

  3. Click Save changes.

Use a valid http: or https: URL. Invalid values show Enter a valid login page URL, including https://.

Related: Use Ferndesk with AI tools · Let readers use your help center in AI tools

Generate the JWT Server-Side

Create an endpoint that returns a signed token. Ferndesk does not require iss (issuer) or aud (audience) claims.

Required claims:

  • sub (string, required): Unique ID in your system that identifies this user. This is the primary identity key.

  • email (string, required): User's email address

  • exp (number, required): Token expiration timestamp

  • iat (number, required): Token issuance timestamp

Optional claims:

  • name (string, optional): Display name

  • customAttributes (object, optional): Extra user attributes. You can also use metadata; both keys are accepted.

The sub claim must remain stable for each user. Ferndesk uses this subject to identify and link users. If a user's sub changes, they will not be linked to their previous help center identity.

Node.js example:

const jwt = require('jsonwebtoken');

app.get('/api/ferndesk-token', async (req, res) => {
  if (!req.user) return res.status(401).json({ error: 'Not authenticated' });

  const now = Math.floor(Date.now() / 1000);
  const token = jwt.sign({
    sub: req.user.id,
    email: req.user.email,
    name: req.user.name,
    iat: now,
    exp: now + 3600, // 1 hour
    customAttributes: { plan: req.user.plan }
  }, process.env.FERNDESK_JWT_SECRET, { algorithm: 'HS256' });

  res.send(token);
});

Python example:

import jwt
import time

@app.route('/api/ferndesk-token')
def ferndesk_token():
    if not current_user:
        return {'error': 'Not authenticated'}, 401

    now = int(time.time())
    token = jwt.encode({
        'sub': current_user.id,
        'email': current_user.email,
        'name': current_user.name,
        'iat': now,
        'exp': now + 3600,
        'customAttributes': {'plan': current_user.plan}
    }, os.environ['FERNDESK_JWT_SECRET'], algorithm='HS256')

    return token

Never expose your JWT secret in client-side code. Store it in environment variables server-side only.

Call Identify from Your Frontend

Fetch the token from your backend and pass it to the SDK:

Ferndesk('init', { widgetId: 'your-widget-id' });

fetch('/api/ferndesk-token').then(r => r.text())
  .then(jwt => Ferndesk('identify', { jwt }))
  .catch(err => console.error('Identification failed:', err));

React example:

useEffect(() => {
  window.Ferndesk('init', { widgetId: 'your-widget-id' });

  if (currentUser) {
    fetch('/api/ferndesk-token')
      .then(r => r.text())
      .then(jwt => window.Ferndesk('identify', { jwt }));
  }
}, [currentUser]);

Call identify after init but before opening the widget. To log out, reinitialize without calling identify.

Verify It's Working

Check these indicators:

  • Browser console: No errors. Invalid JWTs show Ferndesk: identify failed - invalid jwt

  • Contact form: Email and name will be pre-filled

  • Analytics: User sessions appear in your dashboard

Common Errors

Ferndesk: identify requires a jwt

Missing jwt parameter. Check that your backend is returning a JWT string.

invalid jwt

Signature verification or claim validation failed. Verify:

  • Correct JWT secret matches what's stored in Ferndesk

  • Token hasn't expired

  • Algorithm is HS256

  • Required claims are present: sub, email, exp, and iat

JWT subject does not match the existing help-center user

This error occurs when a user's email already exists in your help center but with a different subject ID. This means someone previously signed in with that email using a different identity system or sub value.

To resolve:

  • Ensure your backend always sends the same sub for each user

  • If you've changed user ID systems, the affected user will need to be re-provisioned in Ferndesk

must be called from same domain or 1-level subdomain

Domain mismatch. Your app and help center must share a root domain.

The sub claim is the primary identifier for users. While email is required, Ferndesk binds identity to the subject, not the email address alone. This prevents account takeover if email addresses change or are reused.

Security Notes

  • Set token expiration (1 hour is common)

  • Only generate tokens for authenticated users

  • Use HTTPS everywhere

  • Never commit secrets to version control

  • Keep your sub values stable. Ferndesk persists identity by subject.

Was this helpful?