Widget SSO
Sign identity tokens on your server and sign users into your public board without a second login.
Anyone can type Feeblo.identify({ id: "u_1" }) into a browser console. A signed token
is what makes an identity trustworthy: you sign a JWT on your server with your
organization’s secret, Feeblo verifies it against the same secret, and posts submitted
with it get attached to a real contact.
The same token doubles as a login for your public feedback board.
Signing the token
Find your organization secret in Settings → Security. Sign with HS256 on your server, never in the browser:
import jwt from "jsonwebtoken";
const now = Math.floor(Date.now() / 1000);
const token = jwt.sign(
{
// Required claims
sub: user.id, // your user's ID in your system
aud: process.env.FEEBLO_ORG_ID, // your Feeblo organization ID
exp: now + 10 * 60, // expires in 10 minutes
// Profile claims
email: user.email,
name: user.name,
// Optional
iss: "https://app.yourdomain.com", // identifies who minted the token
iat: now,
avatar: user.avatar,
// Optional. Keys must match custom attributes you defined in Feeblo;
// unmatched keys are ignored.
customFields: {
plan: "enterprise",
},
companies: [
{
id: company.id, // required
name: company.name, // required
// avatar: "...", // optional
customFields: {
industry: "SaaS", // optional, org-defined
},
},
],
},
process.env.FEEBLO_ORG_SECRET,
{ algorithm: "HS256" }
);
Claims reference
| Claim | Required | Notes |
|---|---|---|
sub |
yes | Your user’s ID in your system |
aud |
yes | Your Feeblo organization ID |
exp |
yes | Unix expiration timestamp |
iat |
no | Unix issued-at timestamp |
iss |
no | Who minted the token, e.g. your app’s URL |
email |
yes | |
name |
yes | |
avatar |
no | Image URL |
customFields |
no | Mapped to your organization’s contact attributes; unknown keys ignored |
companies |
no | Array of company objects |
companies[].id |
yes | Per company |
companies[].name |
yes | Per company |
companies[].avatar |
no | Per company |
companies[].customFields |
no | Mapped to your organization’s company attributes |
Passing the token to the SDK
Feeblo.identify({
id: user.id,
email: user.email,
name: user.name,
token,
});<FeebloProvider organizationId="org_123" user={{ ...identity, token }}>
<App />
</FeebloProvider>Signing users into your public board
Your public feedback board accepts the same token as a login. Two ways to hand it over:
Let the SDK decorate your links (recommended). Mark any anchor pointing at your board:
<a href="https://feedback.yourdomain.com" data-feeblo-link>Give feedback</a>
When an identified user interacts with that link, the SDK appends their current token
automatically. It goes in the URL fragment (#ssoToken=...) rather than the query
string, so it never reaches server logs, proxies, or the Referer header. This only works
after identify ran with a token.
Redirect with the token in the URL. Send the user to:
https://feedback.yourdomain.com/?ssoToken=JWT
The board reads the token and strips it before rendering.
Rotating the organization secret
Rotate the secret in Settings → Security whenever someone with access leaves or you suspect a leak. Tokens signed with the old secret fail verification from that moment on, so deploy your updated secret right after you rotate. Rotate during low traffic to keep the gap painless.
Short-lived tokens make rotation cheap: with 10-minute tokens, any damage from an exposed secret lasts 10 minutes.
Troubleshooting
Tokens get rejected
Work through this list:
- You signed with the current organization secret, not a rotated-out one.
audequals your organization ID exactly.sub,email,name, andexpare all present.exphasn’t passed on the server’s clock. Compare clocks if rejections cluster around token birth and expiry times.