> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dialai.ca/llms.txt
> Use this file to discover all available pages before exploring further.

# Signed-in Visitors

> Let a web widget know which customer is signed in to your portal, and give the flow verified values from the customer's token.

A [web widget](/web-widgets) can sit behind the login of your portal. Your portal gives the widget a signed token (a JWT) for the customer who is signed in. The platform checks the token, and then it knows the customer. Values from the claims of the token become **verified context parameters** of the conversation. Your flow can trust these values, because the visitor cannot change them.

Use this when a flow must act for one customer, for example to look up the balance of the customer's account.

## How it works

1. A customer signs in to your portal.
2. The widget asks your page for a token. Your page gets a token for the customer from your identity provider or from your backend.
3. The platform checks the signature, the issuer, the audience and the age of the token.
4. The platform reads the user id and the context parameters from the claims of the token, with the paths that you set.
5. The conversation starts. Functions in the flow read the context parameters as [internal parameters](/functions-and-events#inputs).

The same user of one sign-in provider is the same visitor each time. A staff member who uses **Act as a customer** never sees the conversations of a real customer.

## Who can chat

On the page of the widget, click **Edit** on the **Sign-in** card. In **Who can chat**, select a mode:

| Mode | Label in the list | What the widget does |
| - | - | - |
| Anyone can chat. The widget accepts no sign-in. | **Anonymous** | It does not ask your page for a token. Each visitor is anonymous. This is the default. |
| Anyone can chat. A signed-in visitor is known to the agent by the claims of the portal's token. | **Sign-in optional** | Without a signed-in customer, it starts an anonymous session. When a customer signs in, it ends the anonymous session and starts a verified one. The anonymous conversation does not move to the customer. |
| Only signed-in visitors can chat. | **Sign-in required** | It starts only verified sessions. Without a signed-in customer, it shows "Sign in to chat." and starts nothing. |

The two sign-in modes need at least one sign-in provider.

When the customer on the page changes, or signs out, the widget clears the conversation at once. It then starts again as the new customer, as an anonymous visitor (**Sign-in optional**), or with "Sign in to chat." (**Sign-in required**).

## Add a sign-in provider

A sign-in provider is an issuer of tokens that the widget trusts, such as your identity provider. A widget can have a maximum of 20 providers. The `iss` claim of a token selects the provider.

<Steps>
  <Step title="Open the sign-in settings">
    On the page of the widget, click **Edit** on the **Sign-in** card.
  </Step>

  <Step title="Add a provider">
    Click **Add a sign-in provider**.
  </Step>

  <Step title="Fill in the fields">
    Fill in the fields in the table below.
  </Step>

  <Step title="Add the context parameters">
    Click **Add a parameter** for each value that the flow needs. See [Map claims to context parameters](#map-claims-to-context-parameters).
  </Step>

  <Step title="Save">
    Select the mode in **Who can chat**, then click **Save**.
  </Step>
</Steps>

<Frame caption="The Sign-in card with one sign-in provider">
  <img className="block dark:hidden" src="https://mintcdn.com/dialai/H76LnNRjy-o0z9Jm/images/web-widget-sign-in-editor-light.png?fit=max&auto=format&n=H76LnNRjy-o0z9Jm&q=85&s=38bbd3284ad076194af830eb36f809da" alt="The Sign-in card. Sign-in is optional, and one provider has an issuer, an audience, a JWKS URL, a user id path and one context parameter (light)" width="1007" height="816" data-path="images/web-widget-sign-in-editor-light.png" />

  <img className="hidden dark:block" src="https://mintcdn.com/dialai/H76LnNRjy-o0z9Jm/images/web-widget-sign-in-editor-dark.png?fit=max&auto=format&n=H76LnNRjy-o0z9Jm&q=85&s=bfc906067661b5172f7f94152f15b9fb" alt="The Sign-in card. Sign-in is optional, and one provider has an issuer, an audience, a JWKS URL, a user id path and one context parameter (dark)" width="1007" height="816" data-path="images/web-widget-sign-in-editor-dark.png" />
</Frame>

| Field | What to type |
| - | - |
| **Issuer (iss)** | The exact `iss` value of the tokens of your portal, such as `https://login.example.com/`. |
| **Audience (aud)** | The `aud` value that each token must have, such as `dialai-widget`. The `aud` claim of a token can be this value or an array that holds it. |
| **How the portal signs its tokens** | **JWKS URL**, **Public key (JWK)** or **Shared secret (HS256)**. See [Keys](#keys). |
| **User id path** | A SQL/JSON path to the user's id in the claims. The default is `$.sub`. |
| **Oldest token, in minutes** | 1 to 1440 (24 hours). The platform refuses a token whose `iat` is older than this. The default is 60. |
| **Context parameters** | The parameters that the widget fills from the claims. |

### Keys

Select how your portal signs its tokens:

| Kind | What to give | Algorithms |
| - | - | - |
| **JWKS URL** | The `https` URL of the JSON Web Key Set of your portal, such as `https://login.example.com/.well-known/jwks.json`. It must be on a public host. | ES256, ES384, RS256, PS256 |
| **Public key (JWK)** | One EC (P-256 or P-384) or RSA public key, as JSON. Paste the public key only. The platform refuses a key that has a private part. | ES256, ES384, RS256, PS256 |
| **Shared secret (HS256)** | A secret of 32 to 512 bytes that your backend also knows. | HS256 |

Use a **JWKS URL** when your identity provider publishes one. When a token names a key that the platform does not know, it reads the key set again. Thus a key rotation needs no change here.

Use a **Shared secret** when your own backend signs a token for the widget. After you save, the secret does not show again. To keep the secret, leave the field empty when you edit the provider. To replace it, type a new secret. If the page shows "This deployment cannot store a shared secret. Use a JWKS URL or a public key.", use one of the other kinds.

<Warning>Keep a shared secret on your server. Never put it in the code of your page.</Warning>

### What the token must have

* `iss`, equal to **Issuer (iss)**.
* `aud`, equal to **Audience (aud)**, or an array that holds it.
* `exp` and `iat`.
* The claim that **User id path** finds. It must be text that is not empty, or a number.

The platform permits 60 seconds of clock difference.

## Map claims with SQL/JSON paths

The **User id path** and each context parameter use a **SQL/JSON path**. This is the path language of PostgreSQL. The platform runs each path on the claims of the token.

Example claims of a token:

```json theme={null}
{
  "iss": "https://login.example.com/",
  "aud": "dialai-widget",
  "sub": "user-8812",
  "iat": 1791100000,
  "exp": 1791100600,
  "accounts": [
    { "number": "100-200", "status": "active" },
    { "number": "100-201", "status": "closed" }
  ],
  "https://example.com/customer_type": "residential"
}
```

| Path | Result on these claims |
| - | - |
| `$.sub` | Finds `"user-8812"`. |
| `$.accounts[*] ? (@.status == "active").number` | Finds `"100-200"`, the number of the one active account. |
| `$."https://example.com/customer_type"` | Finds `"residential"`. Put a claim name in double quotes when it has characters such as `/`, `-` or `.`. |
| `$.accounts[*].number` | Finds two values. This is **ambiguous**, so the platform does not use it. |

### Outcomes

Each path has one of four outcomes:

| Outcome | Means | User id path | Context parameter |
| - | - | - | - |
| **found** | The path finds exactly one value. | The visitor's user id. | The platform sets the parameter. |
| **absent** | The path finds no value. | The sign-in fails. | The platform leaves the parameter out. |
| **ambiguous** | The path finds two or more values. | The sign-in fails. | The platform leaves the parameter out. The conversation starts. |
| **error** | PostgreSQL cannot run the path on these claims. | The sign-in fails. | The platform leaves the parameter out. The conversation starts. |

An **ambiguous** or **error** outcome is a mistake in the path. The visitor cannot correct it. The platform records each one as an error.

Some details about the outcomes:

* In the default `lax` mode, a member that is not in the claims gives **absent**. In `strict` mode, it gives **error**. For example, `strict $.customer.id` gives **error** when the claims have no `customer`.
* An error inside a filter gives **absent**, also in `strict` mode. For example, `strict $.accounts[*] ? (@.number > 1)` compares text with a number. PostgreSQL then finds no value, and gives no error.
* An error outside a filter gives **error**. For example, `$.amount.double() ? (@ > 1)` gives **error** when `amount` is text.
* A user id must be text that is not empty, or a number. Another type makes the sign-in fail.

### The check when you save

When you click **Save**, the platform checks the syntax of each path. PostgreSQL also reads each path. If a path is not correct, the field shows a message. Examples are "This is not a valid SQL/JSON path, such as \$.sub." and "Postgres does not accept this path: …".

This check finds only syntax mistakes. It cannot find a path that finds no value, or two values, in the tokens of your portal. To find those mistakes, use [Act as a customer](#test-the-paths-with-act-as-a-customer).

## Map claims to context parameters

A context parameter is a [parameter](/building-blocks#parameters) of your tenant that the widget fills from the claims of the token.

1. In **Context parameters**, click **Add a parameter**.
2. Select the parameter. To make a new parameter, type its name and select **Add ‘name’**.
3. Type the SQL/JSON path to the value in the claims, such as `$.account.number`.

The widget refers to the parameter by its id, not by its name. If you rename the parameter, the path continues to fill it.

When a conversation starts, the platform checks each value against the JSON schema of its parameter. If a value does not fit the schema, the platform leaves the parameter out. The conversation starts without it.

A provider can map a maximum of 50 parameters. The values of all context parameters must be 4 KiB of JSON or less. If they are more, the sign-in fails.

Functions in the flow read these values as [internal parameters (context parameters)](/functions-and-events#inputs). The values come from a checked token, so a function can trust them.

## Give the widget a token

Your page gives the widget a token through the `identity` option of `init`. `identity` is a function with no arguments. It returns a promise of a token for the customer who is signed in now, or `null` when nobody is signed in.

To use `identity`, call `init` yourself, and remove the `data-tenant`, `data-widget-id` and `data-host` attributes from the script tag. See [Call DialAI before the script loads](/web-widgets#call-dialai-before-the-script-loads).

```html theme={null}
<script>
  window.DialAI = window.DialAI || function () { (DialAI.q = DialAI.q || []).push(arguments) };
  DialAI("init", {
    tenant: "<tenant>",
    widgetId: "<widget id>",
    host: "https://<api host>",
    identity: async function () {
      // Your backend checks the portal session and returns a new token as text.
      const response = await fetch("/session/widget-token", { credentials: "same-origin" });
      if (!response.ok) return null;
      return (await response.text()) || null;
    },
  });
</script>
<script async src="https://<api host>/widget/loader.js"></script>
```

The widget calls the function before each session start, and again about each 8 minutes to refresh the session. Obey these rules:

* Return a new token on each call. The widget keeps no token.
* Return `null` when nobody is signed in.
* Return in less than 10 seconds. The widget counts no answer as `null`.
* Keep the token under 32 KiB.

An error, a rejected promise, or a value that is not text counts as `null`.

When a customer signs out of your portal, also call `DialAI("logout")`. Then the next person on the same browser does not see the conversation.

### Your backend can sign the token

If your portal has no identity provider that issues JWTs, your backend can sign a short token for the widget:

1. Your backend checks the portal session of the customer.
2. It signs a JWT with HS256 and the shared secret of the provider. Or it signs with its own private key, and you give the public key or a JWKS URL to the provider.
3. It puts the verified values in the claims, for example the account number.
4. It returns the token to the `identity` function of your page.

Give the token a short life, for example 10 minutes, and set **Oldest token, in minutes** to match.

## Test the paths with Act as a customer

**Act as a customer** lets you chat on the **Try it** card as a signed-in customer, with claims that you type. You do not need a token from your portal.

You need the `web-widgets:simulate` permission, and the widget must have at least one sign-in provider.

<Steps>
  <Step title="Start">
    On the **Try it** card, click **Act as a customer**.
  </Step>

  <Step title="Select the provider">
    In **Sign-in provider**, select the provider to test.
  </Step>

  <Step title="Type the claims">
    In **Claims (JSON)**, type the claims of a customer's token. The field starts with a test user at the **User id path**. The maximum is 16 KiB.
  </Step>

  <Step title="Chat">
    Click **Start as this customer**. Then open the launcher on the sample site and chat.
  </Step>
</Steps>

The card then shows what the paths of the provider found in your claims:

* **Visitor id** — **Found**, or the reason the sign-in fails, such as "Not in the claims. The sign-in fails."
* Each context parameter — **Set**, **Not in the claims**, or "Does not fit: …" with the schema problem. For an ambiguous path, it shows the values that the path finds. For an error, it shows the PostgreSQL error.

<Frame caption="Act as a customer. The paths found the visitor id and set the context parameter.">
  <img className="block dark:hidden" src="https://mintcdn.com/dialai/H76LnNRjy-o0z9Jm/images/web-widget-sign-in-act-as-customer-light.png?fit=max&auto=format&n=H76LnNRjy-o0z9Jm&q=85&s=e799eaa68386ed5f08b0ec100ed86e16" alt="The Try it card in a simulated session. Visitor id shows Found, ServiceAddress shows Set, and the chat panel shows an answer of the flow (light)" width="1004" height="832" data-path="images/web-widget-sign-in-act-as-customer-light.png" />

  <img className="hidden dark:block" src="https://mintcdn.com/dialai/H76LnNRjy-o0z9Jm/images/web-widget-sign-in-act-as-customer-dark.png?fit=max&auto=format&n=H76LnNRjy-o0z9Jm&q=85&s=fd9811eb64f19436ea8192459d67d552" alt="The Try it card in a simulated session. Visitor id shows Found, ServiceAddress shows Set, and the chat panel shows an answer of the flow (dark)" width="1004" height="832" data-path="images/web-widget-sign-in-act-as-customer-dark.png" />
</Frame>

If a path has a mistake, a warning tells you to correct it in the sign-in settings. A real visitor gets the same result.

Click **Stop** to chat as an anonymous visitor again. The session of **Act as a customer** is a simulated session. It never shows the real conversations of that customer.

## When a sign-in fails

When the platform refuses a token, the widget gets one of these codes. Your developers can see the code in the network tab of the browser.

| Code | Means |
| - | - |
| `invalid_token` | Not a JWT, a bad signature, an algorithm that the key does not permit, no `exp` or `iat`, or an `iat` or `nbf` in the future. |
| `unknown_issuer` | No provider of the widget has the `iss` of the token. |
| `expired` | The `exp` of the token is more than 60 seconds ago. |
| `too_old` | The `iat` of the token is older than **Oldest token, in minutes**. |
| `bad_audience` | The `aud` of the token does not hold the audience of the provider. |
| `no_user_id` | The **User id path** found no value, two or more values, a value that is not text or a number, or an error. |
| `context_too_large` | The context parameters are more than 4 KiB of JSON. |
| `identity_required` | A refresh had no token, but the session needs a signed-in customer. The session ends. |
| `user_changed` | A refresh had a token of a different user. The session ends. |

## Best practices

| Practice | Reason |
| - | - |
| Put only the values that the flow needs in the claims. | The values must be 4 KiB of JSON or less. |
| Use a filter to select one value from a list. | A path that finds two values is ambiguous, and the platform does not use it. |
| Test each provider with **Act as a customer** before you go live. | The save check finds only syntax mistakes. |
| Give tokens a short life. | A stolen token is then useful for a short time only. |

## Related

<CardGroup>
  <Card title="Web Widgets" icon="comment" href="/web-widgets">Create a widget and put it on your site.</Card>
  <Card title="Widget appearance" icon="palette" href="/web-widget-appearance">Presets, colours, fonts and the launcher.</Card>
  <Card title="Functions and events" icon="code" href="/functions-and-events">Read context parameters in a function.</Card>
  <Card title="Building blocks" icon="cubes" href="/building-blocks">Make the parameters that the widget fills.</Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.