# Verify your users with a signed token

Sign a token for the signed-in user on your backend and pass it to the widget. The agent then treats the conversation as that user's. For what verification changes, see [Identity verification](https://docs.faustine.ai/web-agents/identity-verification/).

## Before you begin

- The agent is installed in your app. See the [React quickstart](https://docs.faustine.ai/technologies/react/).
- Your backend has an endpoint that returns the signed-in user, such as `GET /api/me`.

## Copy the secret

To copy the agent's secret:

1. In the portal, go to [**Web Agents**](https://auth.faustine.ai/go?to=%2Fweb-assistants) and select your agent.
2. Select the **Settings** tab, then **Identity verification**.
3. In the **Secret** row, select **Copy**.
4. Add the secret to your backend's environment as `FAUSTINE_IDENTITY_SECRET`.

> **Warning:** The secret signs identities for every user of your app. Keep it in server-side configuration only. Do not put it in a variable that your build exposes to the browser.

## Sign the token on your backend

Sign a JWT with HS256 when you return the signed-in user. `sub` is your ID for the user and is required. `name` and `email` are optional and appear in the agent's **Monitoring** tab.

**Node.js**

```bash
npm install jsonwebtoken
```

```js filename="routes/me.js"
const jwt = require('jsonwebtoken');

function faustineToken(user) {
  return jwt.sign(
    { sub: user.id, name: user.name, email: user.email },
    process.env.FAUSTINE_IDENTITY_SECRET,
    { algorithm: 'HS256', expiresIn: '1h' },
  );
}

app.get('/api/me', requireSession, (req, res) => {
  res.json({ ...req.user, faustineJwt: faustineToken(req.user) });
});
```

**Next.js**

`@faustine-ai/next` signs the token for you. It reads `FAUSTINE_IDENTITY_SECRET` and the token expires after 3600 seconds.

```ts filename="app/layout.tsx"
import { faustineIdentityToken } from '@faustine-ai/next';

const user = await getSession();
const identityJwt = user
  ? faustineIdentityToken({ id: user.id, name: user.name, email: user.email })
  : null;
```

Pass `identityJwt` to `<FaustineAgent>`. The component calls `identify` for you.

## Pass the token to the widget

Call `identify` with the token when your app knows who the user is, and `logout` when they sign out.

```js filename="src/auth.js"
import Faustine from '@faustine-ai/web-agent';

export async function onSignedIn() {
  const me = await fetch('/api/me').then((r) => r.json());
  Faustine.identify({ jwt: me.faustineJwt });
}

export function onSignedOut() {
  Faustine.logout();
}
```

`identify` needs an install that calls `init`: the npm loader or the manual script tag. On a page that loads the agent with the auto-mount script tag, `identify` does nothing.

`identify` can run before the widget has finished loading. The call is queued and applied when the widget is ready. `logout` clears the identity and the open conversation from the browser.

The token expires at the time you set, one hour in this example. From then on the conversation runs as anonymous. For sessions that last longer, fetch a new token and call `identify` again, for example each time your app refreshes its own session.

In the portal, open the agent's **Monitoring** tab. The next conversation from that user shows their name with a **Verified** badge.

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| The user shows as anonymous | The token is expired, or the backend signed it with another agent's secret. | Check `FAUSTINE_IDENTITY_SECRET` against the **Secret** row of this agent and sign a new token. |
| The user shows as **Unverified** | The app passes `userId`, `name` or `email` to `identify`. | Pass `{ jwt }` only. |
| The previous user's conversation is still open after signing out | The app does not call `logout`. | Call `Faustine.logout()` in your sign-out handler. |

## Next steps

- [Identity verification](https://docs.faustine.ai/web-agents/identity-verification/): turn on **Enforce verification** once every call sends a token.
- [Restrict the domains that can load the agent](https://docs.faustine.ai/web-agents/restrict-domains/): limit where the widget runs.
