# Identify users

> Sign in your users in the widget, so their conversations follow them and agents see who they are.

## How it works

```js title="app.js"
// token: an identity token that your server signed for the signed-in user
Iris.identify({ token });
```

1. Your server signs an identity token with your identity secret.
2. Your page passes the token to the widget with `Iris.identify`.
3. Iris checks the signature and links the contact to your user's `sub`. Earlier anonymous conversations move to that contact.

> **Keep the identity secret on your server**
>
> Anyone with the identity secret can sign in as any of your users. Never put it in browser code, a mobile app or a public repository.
>
> If it leaks, an admin rotates it in **Settings → Install**. The old secret stops working at once.

## Get the identity secret

An admin copies the identity secret from **Settings → Install** in the dashboard. Only admins see it. Store it in your server's environment as `IRIS_IDENTITY_SECRET`.

```text title=".env"
IRIS_IDENTITY_SECRET=<your identity secret>
```

## Sign an identity token on your server

Sign a new identity token on each page load, or cache it until it nears `exp`. Every claim is in [Identity token claims](/reference/identity-token-claims/).

**Node · jose**

```js title="server.js"
import { SignJWT } from "jose";

// user is the signed-in user in your product.
const token = await new SignJWT({
  email: user.email,
  name: user.name,
  attributes: { plan: user.plan, role: user.role },
})
  .setProtectedHeader({ alg: "HS256", typ: "JWT" })
  .setSubject(user.id)
  .setExpirationTime("1h")
  .sign(new TextEncoder().encode(process.env.IRIS_IDENTITY_SECRET));
```

**Python · PyJWT**

```py title="server.py"
import os
from datetime import datetime, timedelta, timezone

import jwt

# user is the signed-in user in your product.
token = jwt.encode(
    {
        "sub": user.id,
        "email": user.email,
        "name": user.name,
        "attributes": {"plan": user.plan, "role": user.role},
        "exp": datetime.now(timezone.utc) + timedelta(hours=1),
    },
    os.environ["IRIS_IDENTITY_SECRET"],
    algorithm="HS256",
)
```

## Pass it to the widget

Call `Iris.identify` after `Iris.init`. Call it again with a new token before the old one expires. Call `Iris.logout` when the user signs out.

```js title="app.js"
// After the user signs in
const { token } = await fetch("/api/iris-token").then((res) => res.json());
Iris.identify({ token });

// When the user signs out
Iris.logout();
```

## Check your code with the test token

Sign these claims with this identity secret. Your code must print exactly this identity token. If it does not, compare the header and the order of the claims.

```text title="Test identity secret"
7c1e4b9a2f6d0e8c3b5a1f9d7e2c4a6b8d0f3e5c7a9b1d2e4f6a8c0e2b4d6f8a
```

```json title="Claims"
{
  "sub": "user_8421",
  "email": "maria@example.com",
  "name": "Maria Lopez",
  "attributes": {
    "plan": "pro",
    "seats": 12
  },
  "exp": 1893456000
}
```

**Node · jose**

```js title="test-token.mjs"
import { SignJWT } from "jose";

const secret = new TextEncoder().encode("7c1e4b9a2f6d0e8c3b5a1f9d7e2c4a6b8d0f3e5c7a9b1d2e4f6a8c0e2b4d6f8a");

const token = await new SignJWT({
  "sub": "user_8421",
  "email": "maria@example.com",
  "name": "Maria Lopez",
  "attributes": {
    "plan": "pro",
    "seats": 12
  },
  "exp": 1893456000
})
  .setProtectedHeader({ alg: "HS256", typ: "JWT" })
  .sign(secret);

console.log(token);
```

**Python · PyJWT**

```py title="test_token.py"
import jwt

token = jwt.encode(
    {
        "sub": "user_8421",
        "email": "maria@example.com",
        "name": "Maria Lopez",
        "attributes": {
            "plan": "pro",
            "seats": 12
        },
        "exp": 1893456000
    },
    "7c1e4b9a2f6d0e8c3b5a1f9d7e2c4a6b8d0f3e5c7a9b1d2e4f6a8c0e2b4d6f8a",
    algorithm="HS256",
)

print(token)
```

```text title="Expected identity token"
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzg0MjEiLCJlbWFpbCI6Im1hcmlhQGV4YW1wbGUuY29tIiwibmFtZSI6Ik1hcmlhIExvcGV6IiwiYXR0cmlidXRlcyI6eyJwbGFuIjoicHJvIiwic2VhdHMiOjEyfSwiZXhwIjoxODkzNDU2MDAwfQ.0RrzrVC8W-h9B_rrS9KlmwI67hD1bmMnTeaPesGUUmo
```

Then sign in to your product, open the widget and send a message. In the dashboard inbox, the conversation shows the contact's name and email.

## Expiry and secret rotation

- An identity token stops working at its `exp`. After that, the session request fails with `401`.
- When an admin rotates the identity secret, every token signed with the old secret fails at once. Put the new secret in `IRIS_IDENTITY_SECRET` and deploy your server right after.

## Fix common problems

| Message | What happened | Fix |
| --- | --- | --- |
| `Iris: <method> ran before Iris.init, so nothing changed.` | `identify`, `logout` or `update` ran before `Iris.init`. After an early `identify`, the contact stays anonymous. | Call `Iris.init({ appId })` first. Calls made before `iris.js` loads wait in a queue, in order. |
| `Iris: identify got no identity token, so the contact stays anonymous.` | The token is empty or not a string. Often your token endpoint failed. | Check that your server returns an identity token, then pass it as `Iris.identify({ token })`. |
| `401` on the session request | Iris rejected the identity token. It expired, its signature does not match the identity secret, or `sub` or `exp` is missing. The console shows `Iris: <action> failed, because Iris rejected the identity token.` | Sign a new identity token with the current identity secret and both required claims. Check your code with the [test token](/guides/identify-users/#check-your-code-with-the-test-token). |
