Skip to content

Docs

Signed SSO identity

Identify your logged-in users with a short-lived server-signed token.

If your users are already signed in to your product, you can identify them in the widget so their votes, comments and subscriptions belong to their real account — no email codes, works across devices.

Never put your identity secret in the browser. Tokens must be signed on your server. Anyone holding the secret can impersonate your users.

How it works

  1. Copy your identity secret from Settings → Widget → Identity into your server's environment (for example FEEDBACK_IDENTITY_SECRET).
  2. When a logged-in user loads a page, your backend signs a JWT with HS256 using that secret.
  3. Your page calls window.FeedbackWidget.identify({ token }).
  4. Our backend validates the signature, audience and expiry, maps sub to a visitor record in your workspace, and issues a restricted widget session.

Token claims

ClaimRequiredValue
subYesYour stable user id (string, max 191 chars)
audYesYour public workspace key, e.g. pub_…
issRecommendedYour app's identifier, e.g. https://app.example.com
iatYesIssued-at (seconds since epoch)
expYesExpiry — at most 5 minutes after iat
email, name, avatar_urlOptionalProfile shown to your team (email is never shown publicly; avatar must be https)

Node.js

// npm install jsonwebtoken
import jwt from 'jsonwebtoken';

export function feedbackToken(user) {
  return jwt.sign(
    { sub: String(user.id), email: user.email, name: user.name },
    process.env.FEEDBACK_IDENTITY_SECRET,
    { algorithm: 'HS256', audience: 'pub_YOUR_PUBLIC_WORKSPACE_KEY', issuer: 'https://app.example.com', expiresIn: '5m' }
  );
}

PHP

// composer require firebase/php-jwt
use Firebase\JWT\JWT;

function feedbackToken(array $user): string {
    $now = time();
    return JWT::encode([
        'sub' => (string) $user['id'],
        'aud' => 'pub_YOUR_PUBLIC_WORKSPACE_KEY',
        'iss' => 'https://app.example.com',
        'iat' => $now,
        'exp' => $now + 300,
        'email' => $user['email'],
        'name' => $user['name'],
    ], getenv('FEEDBACK_IDENTITY_SECRET'), 'HS256');
}

Python

# pip install PyJWT
import os, time, jwt

def feedback_token(user):
    now = int(time.time())
    return jwt.encode({
        "sub": str(user.id),
        "aud": "pub_YOUR_PUBLIC_WORKSPACE_KEY",
        "iss": "https://app.example.com",
        "iat": now,
        "exp": now + 300,
        "email": user.email,
        "name": user.name,
    }, os.environ["FEEDBACK_IDENTITY_SECRET"], algorithm="HS256")

In the browser

// Fetch a fresh token from YOUR backend, then identify.
const { token } = await fetch('/api/feedback-token', { credentials: 'include' }).then(r => r.json());
window.FeedbackWidget.onReady(() => window.FeedbackWidget.identify({ token }));

// On sign-out of your app:
window.FeedbackWidget.logout();

Hosted portal sign-in

To sign users into your hosted portal, POST a token from a form on your site to https://ext0.com/p/{your-slug}/sso with fields token and optional return_to (a path inside your portal). Tokens are never accepted in query strings.

Rotation and security

  • Rotate the secret any time from settings; the previous secret keeps working briefly so you can deploy without downtime.
  • Tampered, expired, wrong-audience or wrong-workspace tokens are rejected.
  • Changing identity invalidates the previous widget session. Users cannot change their email or id from JavaScript.
  • If SSO is not required by your privacy settings, guests can still participate without a token.