Authenticated widget integration with Ruby on Rails

This guide shows how a customer Rails backend gives the Guanta widget a verified user identity while retaining ownership of authentication, sessions, users, and authorization.

Return to the live Rails example ยท View the source repository

The trust boundary

Customer Rails applicationGuanta
Authenticates users and owns its sessionTrusts the backend's signed assertion
Stores passwords, SSO data, and authorization rulesNever receives passwords, hashes, or Rails cookies
Chooses a stable external ID and display nameMakes the trusted identity available to the configured agent
Creates an opaque value for each successful loginBinds conversation continuity to that exact login context

Never accept the asserted user ID, name, or login context from browser parameters. Derive all three from authenticated backend state.

End-to-end flow

Sequence diagram of the authenticated widget identity flow
  1. Rails authenticates the visitor, rotates its session, and generates a new opaque login context.
  2. The standard Guanta loader calls the same-origin POST /widget-identity route when it initializes.
  3. The controller reads the current user exclusively from the encrypted Rails session.
  4. Guanta::Client calls the tenant identity-token API with a server-only credential.
  5. Rails returns the one-time token to the loader with no-store cache headers.
  6. The loader securely starts or resumes the matching Guanta conversation.

Configuration

Obtain the base URL, public widget ID, and SRI integrity value from the interface Playground. Create the server credential under Widget Identity Credentials for that same interface. Add the application's exact origin to the interface's allowed origins.

APP_URL=https://customer.example
APP_SESSION_SECURE=true

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

The credential is a backend secret. Do not place it in HTML, JavaScript, Git, logs, screenshots, analytics, or browser storage.

Reusable Rails client

The example isolates Guanta API behavior in app/services/guanta/client.rb and app/services/guanta/http_transport.rb. Both are independent of the demo controllers and include YARD comments for every method.

client = Guanta::Client.new(
  base_url: Rails.application.credentials.dig(:guanta, :base_url),
  widget_public_id: Rails.application.credentials.dig(:guanta, :widget_public_id),
  identity_credential: Rails.application.credentials.dig(:guanta, :identity_credential)
)

token = client.create_widget_identity_token(
  external_user_id: current_user.id.to_s,
  full_name: current_user.full_name,
  login_context_id: session.fetch(:widget_login_context_id)
)

render json: { identity_token: token.value }

The method raises Guanta::Client::ConfigurationError for invalid local values and Guanta::Client::RequestError for connectivity, rejection, or malformed-response failures.

Identity-token request

POST /api/v1/widget-identity-tokens
Authorization: Bearer wic_credential.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",
  "login_context_id": "opaque-per-login-value"
}

Conversation continuity

The identity token is short-lived and single-use. The loader separately stores an encrypted Guanta resume token and a visibility preference in the current tab's sessionStorage. On each new Rails page it obtains a fresh identity token. Guanta resumes only when the widget, origin, user, login context, active conversation, and expiry all match.

Login and logout use reset_session. Each login receives a new SecureRandom.hex(32) context, so an old browser-held token cannot restore an earlier authenticated conversation. A new browser tab also starts closed because tab storage is separate.

Production checklist