Skip to content

Authentication

Uchara provides three authentication methods for different use cases:

Use Case: Embedding chat widget in customer-facing applications

Flow:

  1. Get widget token from channel settings (public, embedded in HTML)
  2. Client calls POST /v1/widget/session with widget token
  3. Server creates/retrieves contact, returns short-lived JWT (24h)
  4. Client uses JWT for subsequent REST + WebSocket requests

Security:

  • Widget token is public (safe to embed in client-side code)
  • JWT is scoped to single contact
  • No sensitive operations allowed

Example:

const visitor = new VisitorSDK({
apiURL: 'https://api.uchara.com',
widgetToken: 'wgt_abc123', // Public token
});
await visitor.init(); // Gets JWT, stores internally

Use Case: Building custom agent dashboards

Flow:

  1. Client calls POST /v1/auth/login with email/password
  2. Server validates credentials, returns access + refresh tokens
  3. Access token expires in 15 minutes, refresh token in 7 days
  4. Client refreshes with POST /v1/auth/refresh

Security:

  • Credentials transmitted over HTTPS only
  • Access token short-lived (15 minutes)
  • Refresh token stored securely (httpOnly cookie or secure storage)

Example:

agent = AgentSDK(api_url="https://api.uchara.com")
response = agent.login(
email="agent@company.com",
password="..."
)
# Access token auto-set, refresh handled by SDK

Use Case: Backend-to-backend integration

Flow:

  1. Generate API key in workspace settings (admin only)
  2. Client sends API key in Authorization: Bearer <key> header
  3. Server validates key, extracts workspace context

Security:

  • API key is secret — never commit to git, use env vars
  • Scoped to workspace, full permissions
  • Rotate regularly (90 days recommended)
  • Can be revoked instantly

Key Format: uchara_sk_<32_random_chars>

Example:

client := uchara.NewServerSDK(uchara.ServerConfig{
APIURL: "https://api.uchara.com",
APIKey: os.Getenv("UCHARA_API_KEY"),
})

Use Case: Seamless, secure handoff from your own authenticated backend to the Uchara dashboard without ever exposing an API key or JWT in the browser.

Flow:

  1. Your backend (authenticated with a Server SDK API key) calls POST /v1/auth/sso/ticket with the target member’s email.
  2. The server validates the member (and optional channel scope), then returns a redirect_url carrying a short-lived, one-time opaque ticket in the URL fragment: https://app.uchara.com#sso_ticket=<one-time-ticket>.
  3. Your backend returns that redirect_url to the browser from your own authenticated endpoint.
  4. The dashboard bootstrap reads the ticket from the fragment, exchanges it via POST /v1/auth/sso/exchange, and strips it from the URL.

Endpoint auth distinction:

  • POST /v1/auth/sso/ticketAPI-key-only (server-to-server). Never call this from the browser.
  • POST /v1/auth/sso/exchangepublic, but accepts only the opaque one-time ticket. It never accepts an API key or JWT.

Request (POST /v1/auth/sso/ticket):

{
"email": "agent@company.com",
"channel_ids": ["<channel-uuid>"]
}

Response:

{
"ok": true,
"data": {
"redirect_url": "https://app.uchara.com#sso_ticket=<one-time-ticket>"
}
}

Scope semantics (channel_ids):

  • Omitted → full workspace access.
  • Explicit [] → valid zero-channel scope.
  • Explicit array → validated against the workspace; unknown or duplicate IDs are rejected.
  • Explicit JSON null → rejected (must be omitted or an array).

Ticket lifetime & single-use:

  • The ticket is short-lived (60 seconds) and one-time.
  • It is consumed atomically on exchange; a second exchange, an expired ticket, or an unknown ticket returns 401 Unauthorized.
  • Only the hash of the ticket is stored server-side; the raw value is never persisted.

Security:

  • Keep the API key backend-only — never expose it in browser code.
  • Do not log, store, or reuse redirect_url values.
  • The ticket travels in the URL fragment (#...), so it is never sent to the server or written to access logs.
  • No JWT, API key, or refresh token ever appears in the URL.

API keys can be generated from the workspace settings:

Terminal window
# Via API (requires admin JWT)
POST /v1/api-keys
{
"name": "Production Server",
"expires_at": "2027-12-31T23:59:59Z"
}
# Response (key shown only once!)
{
"id": "...",
"key": "uchara_sk_abc123...",
"key_prefix": "uchara_sk_ab...",
"created_at": "..."
}
  • Call init() once on app start
  • JWT expires in 24h — SDK handles refresh automatically
  • Store widget token in config, not hardcoded
  • Store refresh token securely (keychain/keystore on mobile)
  • Implement token refresh before expiry (proactive, not reactive)
  • Clear tokens on logout
  • Load API key from environment variables
  • Rotate keys every 90 days
  • Use separate keys for staging/production
  • Revoke compromised keys immediately
Token TypeLifetimeRefresh
Visitor JWT24 hoursAuto on init()
Agent Access Token15 minutesVia refresh token
Agent Refresh Token7 daysRe-login required
API KeyCustom (default: no expiry)Rotate manually
SSO Ticket60 secondsOne-time exchange

All SDKs normalize authentication errors:

try {
await sdk.sendMessage(conversationId, { content: 'Hello' });
} catch (error) {
if (error.status === 401) {
// Token expired, re-authenticate
await sdk.login(...);
}
}
  1. Never commit secrets - Use environment variables
  2. HTTPS only - All requests over TLS
  3. Rotate regularly - API keys every 90 days
  4. Scope appropriately - Use least privilege
  5. Monitor usage - Track API key last_used_at
  6. Revoke on breach - Instant revocation via API