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
- Copy your identity secret from Settings → Widget → Identity into your server's environment (for example
FEEDBACK_IDENTITY_SECRET). - When a logged-in user loads a page, your backend signs a JWT with HS256 using that secret.
- Your page calls
window.FeedbackWidget.identify({ token }). - Our backend validates the signature, audience and expiry, maps
subto a visitor record in your workspace, and issues a restricted widget session.
Token claims
| Claim | Required | Value |
|---|---|---|
sub | Yes | Your stable user id (string, max 191 chars) |
aud | Yes | Your public workspace key, e.g. pub_… |
iss | Recommended | Your app's identifier, e.g. https://app.example.com |
iat | Yes | Issued-at (seconds since epoch) |
exp | Yes | Expiry — at most 5 minutes after iat |
email, name, avatar_url | Optional | Profile 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.