# Identify signed-in users

> Tell Giov Copilot who is signed in with a userHash from your server, keep it in sync in single-page apps, and pass API tokens for browser actions.

Copilot only switches on for a user your server vouches for. Your server signs the user's ID with your workspace identity secret, and your page passes the ID and the signature (`userHash`) to the widget. Giov checks the hash before Copilot starts. Without a valid hash, the widget stays in support mode.

This is the same check that [Identify visitors](/docs/widget/identify-visitors) uses for the support widget. It uses the same secret, so if you already identify visitors, step 2 of the Copilot setup is done.

## 1. Create the identity secret

Open **Settings → Chat widget → Visitor details**, turn on **Signed-in users** and select **Generate secret** (or rotate an existing one). Store it in your server configuration, for example as `GIOV_IDENTITY_SECRET`.

> [!WARNING]
> Keep the secret on your server. Never put it in browser code, a frontend `.env` file or your repository. Anyone who has it can sign in as any of your users.

## 2. Compute userHash on your server

`userHash` is the HMAC-SHA256 of the user's ID, as lowercase hex, keyed with the secret exactly as issued. Use the same ID you pass to the widget.

```js title="server.js" tab="Node"
import { createHmac } from "node:crypto";

const userHash = createHmac("sha256", process.env.GIOV_IDENTITY_SECRET)
  .update(String(user.id))
  .digest("hex");
```

```python title="server.py" tab="Python"
import hashlib, hmac, os

user_hash = hmac.new(
    os.environ["GIOV_IDENTITY_SECRET"].encode(),
    str(user.id).encode(),
    hashlib.sha256,
).hexdigest()
```

```php title="server.php" tab="PHP"
<?php
$userHash = hash_hmac('sha256', (string) $user->id, getenv('GIOV_IDENTITY_SECRET'));
```

```ruby title="server.rb" tab="Ruby"
require "openssl"

user_hash = OpenSSL::HMAC.hexdigest("SHA256", ENV["GIOV_IDENTITY_SECRET"], user.id.to_s)
```

```go title="server.go" tab="Go"
import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"os"
)

mac := hmac.New(sha256.New, []byte(os.Getenv("GIOV_IDENTITY_SECRET")))
mac.Write([]byte(userID))
userHash := hex.EncodeToString(mac.Sum(nil))
```

## 3. Pass it to the widget

Render the user's ID and hash into your dashboard page. Don't load a second widget; add them to the installation you already have.

If you install with the script tag, set `window.GiovChatSettings` before it. If you call `ChatWidget.init` yourself, add the values to its `metadata` instead.

```html title="dashboard.html" tab="Script tag"
<script>
  window.GiovChatSettings = {
    userId: "USER_ID",     // the same id your server hashed
    userHash: "USER_HASH", // computed on your server, never in the browser
    firstName: "FIRST_NAME" // optional, so Copilot greets people by name
  };
</script>
<script src="https://widget.giovchat.app/YOUR_WORKSPACE_ID" async></script>
```

```js title="app.js" tab="ChatWidget.init"
ChatWidget.init({
  tenantId: "YOUR_WORKSPACE_ID",
  metadata: {
    userId: "USER_ID",     // the signed-in user's id
    userHash: "USER_HASH", // computed on your server, never in the browser
    firstName: "FIRST_NAME", // optional, so Copilot greets people by name
  },
});
```

`tenantId` is your workspace ID. **Giov Copilot → Website setup** shows this snippet with your workspace ID already filled in.

On a dashboard path, a verified user now gets Copilot. Signed-out users and public pages keep the support chat.

## Single-page apps

If users sign in or out without a full page reload, tell the widget when that happens:

```js title="auth.js"
// Single-page apps: after the user signs in
ChatWidget.identify({ userId, userHash, firstName }); // firstName: optional, for the greeting

// On sign-out
ChatWidget.identify(null);
await ChatWidget.resetSession();
```

- `identify({ userId, userHash, firstName })` verifies the new user and switches to Copilot on dashboard paths. Fetch the `userHash` from your server after sign-in. `firstName` is optional and display-only, so Copilot greets people by name. A different user never inherits the previous name.
- `identify(null)` returns the widget to support mode.
- `resetSession()` clears Copilot's state in the browser straight away, then ends the session with Giov. Call it before sign-out or an account switch, so the next user never sees the previous user's Copilot.

Copilot sessions are kept per browser tab.

## API token headers

Copilot calls your API from the user's browser and sends their cookies. If your API reads a header token such as `Authorization` instead of a cookie, register it once:

```js title="app.js"
// Only if your API authenticates with a header token instead of cookies.
// Copilot calls your API from the user's browser, so it needs the same header.
ChatWidget.setAuthHeaders(() => ({
  Authorization: `Bearer ${getAccessToken()}`,
}));
```

- The function is called for each browser action, so return the current token, and refresh it the way your app normally does. It can also return a promise.
- The headers are kept in memory only. They're never logged or stored.
- They're sent only with Copilot's browser actions, to your approved origins.
- Call `ChatWidget.setAuthHeaders(null)` to remove them, for example on sign-out.

## What a userHash identity can do

A `userHash` proves who the user is, but it doesn't carry roles or scopes. With it, Copilot can:

- run browser actions, which your API authorizes with the user's own session.

It can't:

- use server connections, which act with your backend's credentials;
- run actions that require scopes.

For those, use the signing-key identity.

## Signing-key identity (Advanced)

The signing-key identity is for apps that need roles and scopes, or server connections. Your backend signs a short-lived proof of who is signed in, with a private key only it holds. Copilot checks the proof with the matching public key.

To set it up:

1. In **Giov Copilot → Advanced**, turn on **Use a signing-key session endpoint** and generate a signing key. Giov keeps only the public key. Copy the private key once and store it as a server-only secret named `COPILOT_HOST_PRIVATE_JWK`.
2. Add a same-origin endpoint to your backend, `POST /api/giov-session` by default. It checks your existing login and returns `{ "assertion": "…" }`, plus an optional `user: { "firstName": "…" }` so Copilot greets people by name. The assertion is an ES256-signed token that expires within 60 seconds and lists the user's account, roles and scopes. For a signed-out user it returns a JSON `401`, not a redirect to your login page.
3. Call `ChatWidget.refreshSession()` after a new login, and `ChatWidget.resetSession()` before logout or an account switch.

Publishing a new public key ends existing Copilot sessions, so deploy the new private key at the same time.

> [!NOTE]
> Public documentation for the signing-key identity is coming. Until then, **Giov Copilot → Developer guide** in your dashboard has the full reference: the token's claims, endpoint examples, revocation and troubleshooting.
