Guanta authenticated widget example

Guanta

This framework-free PHP application demonstrates how a customer application can give a standard Guanta chat widget the verified identity of a logged-in user.

The important part of this example is the server-to-server identity exchange. The application continues to own login, passwords, sessions, user records, and authorization. Guanta receives only the trusted user identifier and display name that the application backend chooses to assert.

The included CSV login is intentionally small and is not a production authentication system. It exists only so the complete integration can be run and inspected without a framework or database.

The same guide is also available as a standalone HTML document.

What this example demonstrates

The question “Who am I?” makes the identity boundary easy to see, but an AI response must not be used as an application authorization decision.

Authentication responsibilities

The customer application remains the identity provider for its own users:

Customer application Guanta
Authenticates the user Trusts an assertion made by the authenticated customer backend
Stores passwords, SSO identities, or other login credentials Never receives the user's password or password hash
Maintains the application session Creates a separate widget conversation session
Chooses a stable external user ID and display name Stores that asserted identity with the widget session
Decides what the user may do in the customer application Makes the trusted identity available to the configured Guanta agent

Guanta authentication does not replace the customer's login system. It connects an already-authenticated customer session to a Guanta widget session.

Tenant, agent, widget, and credential relationship

Each customer integration is isolated inside a Guanta tenant:

Guanta tenant
└── Agent
    └── Widget interface
        ├── Public widget ID
        ├── Allowed customer origins
        └── Widget identity credentials
            ├── Customer production backend
            └── Customer staging backend

A credential created in one tenant cannot authenticate against another tenant. A credential for one widget interface cannot issue a usable identity token for a different widget.

It is normal to create multiple credentials for the same widget—for example, separate credentials for staging and production, or for two independently deployed customer backends. This makes rotation and revocation safer.

End-to-end flow

Authenticated widget identity flow

The token is created only when the widget is about to initialize. Creating it when the page first renders could allow it to expire before the visitor opens the widget.

Values supplied by Guanta

The environment-variable names below are conventions used by this example. A customer may use different names in their own application.

Example variable Scope Secret? Source
GUANTA_BASE_URL Tenant and environment No The customer's Guanta tenant URL
GUANTA_WIDGET_PUBLIC_ID Widget interface No The widget loader snippet in the interface Playground
GUANTA_WIDGET_IDENTITY_CREDENTIAL Widget interface and customer backend Yes Created in the interface's Widget Identity Credentials section and shown once
GUANTA_WIDGET_LOADER_INTEGRITY Published widget-loader version No The widget loader snippet in the interface Playground

In the Guanta tenant UI:

  1. Open Agents → Interfaces and select the widget interface.
  2. Open its Playground and use Copy HTML to obtain the loader URL, integrity value, and widget public ID.
  3. Open Widget Identity Credentials for the same interface.
  4. Create a named credential for the customer backend and optionally set an expiry.
  5. Confirm the tenant-user password and copy the complete credential when it is displayed.
  6. Store the credential immediately in the customer application's secret manager. Guanta shows the complete value only once.

The widget's allowed origins must include the customer application's exact origin, including scheme and port where applicable.

Requirements

Local setup

Copy the environment template:

cp .env.example .env

Configure the application and Guanta values:

APP_URL=http://127.0.0.1:8088
APP_SESSION_SECURE=false

GUANTA_BASE_URL=https://your-tenant.example
GUANTA_WIDGET_PUBLIC_ID=your-widget-public-id
GUANTA_WIDGET_IDENTITY_CREDENTIAL=wic_your-credential-id.your-secret
GUANTA_WIDGET_LOADER_INTEGRITY=sha384-your-published-integrity-value

GUANTA_WIDGET_IDENTITY_CREDENTIAL is sensitive. Keep it only in the ignored .env file for local development or in the deployment platform's secret store. Never place it in HTML, JavaScript, source control, screenshots, logs, analytics, or browser storage.

Start the local application:

./scripts/run.sh

Then open http://127.0.0.1:8088.

The PHP built-in server is for local demonstration only. Use a supported web server and PHP runtime for an internet-facing deployment.

Demo users

The committed CSV contains password hashes rather than plaintext passwords. The three local demo identities are:

External ID Email Full name Password availability
1 jtorras@guanta.ai Jordi T Guanta Distributed separately
2 jordi@torras.ai Jordi Torras Distributed separately
3 user@example.com George Towers B_timu_bicu_fila_4315

The public user@example.com credentials are prefilled in the login form so a customer can test the authenticated flow immediately. All demo credentials are for this example only and must not be reused for real accounts.

For a real integration, replace the CSV authentication with the application's existing session, SSO, OAuth, OpenID Connect, Laravel, Symfony, or other authentication mechanism. The Guanta exchange begins only after that system has authenticated the request.

Standard widget embed

The page uses the ordinary Guanta widget loader with one additional attribute pointing to a customer-owned endpoint:

<script
  src="https://your-tenant.example/widget/embed-loader.v1.0.0.js"
  integrity="sha384-your-published-integrity-value"
  crossorigin="anonymous"
  data-public-id="your-widget-public-id"
  data-identity-token-url="/widget-identity.php"
  defer>
</script>

data-identity-token-url must resolve to the customer page's own origin. The loader calls it with POST when the widget initializes and includes the customer application's normal same-origin session cookie.

The endpoint must determine the current user exclusively from the trusted server-side session. It must never accept external_user_id or full_name from browser input.

When no application user is logged in, the endpoint returns anonymous state:

{"identity_token":""}

When a user is logged in, it returns the one-time token received from Guanta:

{"identity_token":"wit_<token-id>.<one-time-secret>"}

Responses containing identity information must use Cache-Control: no-store and should not be logged.

Identity-token API

The customer backend creates a token by calling the tenant-specific endpoint:

POST /api/v1/widget-identity-tokens HTTP/1.1
Host: your-tenant.example
Authorization: Bearer wic_<credential-id>.<secret>
Accept: application/json
Content-Type: application/json

{
  "widget_public_id": "your-widget-public-id",
  "external_user_id": "stable-customer-user-id",
  "full_name": "Customer User"
}

Successful response:

HTTP/1.1 201 Created
Cache-Control: no-store, private
Content-Type: application/json

{
  "identity_token": "wit_<token-id>.<one-time-secret>",
  "widget_public_id": "your-widget-public-id",
  "expires_at": "2026-08-14T12:00:00+00:00"
}

Field guidance:

The current API accepts an external user ID of up to 128 characters and a full name of up to 191 characters. Both values are required.

Typical error responses:

Status Meaning
401 The identity credential is missing or invalid
403 The credential is not authorized for the widget, or the interface is unavailable
422 A required identity field is missing or invalid
429 The credential exceeded the identity-token rate limit

Treat any unsuccessful response as an authenticated initialization failure. Do not silently downgrade a logged-in visitor to anonymous, because that can hide configuration or availability problems.

How the PHP example implements it

  1. public/index.php authenticates a demo user and stores only the user's ID in the PHP session.
  2. The widget loader calls public/widget-identity.php when the widget opens.
  3. widget-identity.php reads the current user from the PHP session.
  4. src/app.php sends the server-to-server request to the tenant's identity-token API.
  5. The same-origin endpoint returns the resulting one-time token to the widget loader.
  6. The widget consumes the token while creating the Guanta conversation session.

The example's identity endpoint additionally requires a POST request marked with X-Requested-With: GuantaWidget. This is a defense-in-depth check, not a replacement for session security, same-origin controls, or CSRF protections appropriate to the customer's application.

Adapting the flow to another backend

The server-side logic is framework-independent:

receive POST from the widget loader
load the current user from the authenticated application session

if there is no current user:
    return { "identity_token": "" }

call the Guanta tenant API using the server-only credential
send widget_public_id, stable external_user_id, and full_name

if Guanta does not return 201:
    fail the authenticated widget initialization

return the one-time identity_token with no-store cache headers

In a production framework, place the Guanta API call in a server-side service and protect the same-origin identity endpoint with the application's normal session middleware. Never let the browser submit or override the asserted user ID.

Login, logout, and account switching

An identity token initializes one widget session; it is not a permanent login token.

This example performs full-page navigation after login and logout, which naturally creates a fresh widget instance.

Security model

The trusted identity is context for the Guanta agent. It does not grant permissions inside the customer application, and agent output must not be treated as proof of authentication or authorization.

Credential lifecycle

Use separate credentials for separate customer environments or independently operated backends. Give each credential a descriptive name and an expiry where appropriate.

For rotation without interruption:

  1. Create a second credential for the same widget interface.
  2. Store and deploy the new credential to the customer backend.
  3. Verify identity-token creation through the new credential.
  4. Revoke the old credential in Guanta.

Regenerating an existing credential invalidates its previous secret immediately. Revoking a credential also prevents unconsumed tokens issued by that credential from being accepted.

Production checklist

Troubleshooting

Symptom What to check
401 Invalid widget identity credential Confirm the complete credential was copied once, is active, has not expired, and belongs to this tenant
403 Credential is not authorized for this widget Confirm the credential and GUANTA_WIDGET_PUBLIC_ID came from the same widget interface
422 response Confirm widget_public_id, external_user_id, and full_name are present and within their limits
429 response Check for repeated initialization loops and the configured rate limit
Example returns 502 from /widget-identity.php Inspect the customer backend's connectivity and Guanta API response without logging credentials or tokens
Widget remains anonymous after login Confirm the customer session cookie reaches the same-origin identity endpoint and that the endpoint returns a token
Token is expired or already used Mint tokens only when the widget opens and never cache or reuse them
Widget does not load Check the loader URL, SRI value, allowed origin, Content Security Policy, and widget public ID
Identity persists after logout or account switch Destroy or reload the existing widget iframe and start a new widget session

Relevant files

Verification

Validate the demo users and password hashes:

php tests/check.php

Rebuild the documentation artifacts after editing this README or the Mermaid source:

./scripts/build-docs.sh

For an end-to-end check:

  1. Open the page while logged out and ask the widget “Who am I?”. It should state that no authenticated identity is available.
  2. Log in with one of the separately supplied demo passwords.
  3. Open the new widget session and ask “Who am I?”. It should answer with that user's configured full name.
  4. Log out and confirm that a newly initialized widget no longer knows the previous identity.
  5. Repeat with the second demo user to verify account isolation.

Getting help

If Guanta rejects a correctly formed request or the required tenant values are unavailable, contact your Guanta representative with the tenant name, widget interface name, timestamp, and HTTP status. Do not include credentials or identity-token values.