---
title: Widget SSO
description:
  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:

```ts
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              |

:::note[Keep tokens short-lived]
Feeblo rejects tokens without `exp` and tokens past their `exp`. Mint a fresh token per
page render or session and keep the window short: 5–15 minutes works well. A leaked
token then expires before anyone can reuse it. If you send `iat`, it must not sit
further in the future than normal clock skew allows.
:::

## Passing the token to the SDK

**Vanilla JS**

```ts
Feeblo.identify({
  id: user.id,
  email: user.email,
  name: user.name,
  token,
});
```

**React**

```tsx
<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:

```html
<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.

:::warning[Query strings travel]
A `?ssoToken=` URL ends up in server logs, proxy logs, browser history, and the Referer
header. Anyone holding the full URL can sign in as the user until the token expires.
Mint a fresh token per redirect, keep its lifetime in minutes, and reserve this method
for redirects your server initiates. Don't put `?ssoToken=` links in emails, bookmarks,
or anywhere else they stick around.
:::

## 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.
- `aud` equals your organization ID exactly.
- `sub`, `email`, `name`, and `exp` are all present.
- `exp` hasn't passed on the server's clock. Compare clocks if rejections cluster around
  token birth and expiry times.

## Next steps

<CardGroup cols={2}>
  <Card title="Identifying users" href="/developers/identity">
    The identity object and custom fields.
  </Card>
  <Card title="Widget API" href="/developers/api">
    The HTTP endpoints behind the widget.
  </Card>
</CardGroup>
