Authentication
By default, everyone who uses the VableAI widget on your site is anonymous. To tell VableAI who a signed-in user is, create an access token on your server with your own login system and pass it to the SDK.
The token is a JSON Web Token (JWT). You sign it with a private key that stays on your server, and VableAI checks it with the matching public key that you upload.
Access tokens work with the React and Vue SDKs. The script tag and Flutter installs don't support them yet.
What a token does
| Feature | With a valid token |
|---|---|
| Greeting | The chat input greets the user by their first name |
| Conversation records | The user's ID, email and name are saved with new conversations |
A token doesn't change anything else:
- Anonymous visitors can still use the widget. A token identifies a user, but VableAI doesn't require one.
- The AI agent doesn't see the token or the user's details. To give the agent context about the user, use
useIntentState. - VableAI doesn't call your APIs with the token. Actions run in the user's browser through intents, so they use the session your app already has.
How it works
sequenceDiagram participant App as Your frontend participant Server as Your server participant Widget as VableAI widget participant Vable as VableAI App->>Server: Request a token, using your own session Server->>Server: Check the session and sign a JWT with your private key Server-->>App: Access token App->>Widget: Pass the token to the SDK Widget->>Vable: Start a conversation with the token Vable->>Vable: Check the signature with your public key Vable-->>Widget: Conversation started
1. Create a signing key
Generate an RSA key pair:
openssl genrsa -out vable_private.pem 2048
openssl rsa -in vable_private.pem -pubout -out vable_public.pem
Keep vable_private.pem in your server's secret storage. Never ship it to the browser or commit it to source control.
2. Upload the public key
- Open the VableAI dashboard and go to Settings > API.
- Under Signing Key, select Set Signing Key.
- Paste the full contents of
vable_public.pem, including the-----BEGIN PUBLIC KEY-----and-----END PUBLIC KEY-----lines.
Each project has one signing key. If you replace it, tokens signed with the old key stop working straight away, so deploy the new private key at the same time.
The signing key isn't the same as the publicKey you pass to the SDK. publicKey identifies your VableAI project, and the signing key verifies your tokens.
3. Sign a token on your server
Add an endpoint that only signed-in users can reach. Sign the token with the RS256 algorithm.
| Claim | Required | Description |
|---|---|---|
sub | Yes | Your unique ID for the user |
exp | Recommended | When the token expires. Tokens without it never expire |
firstName | No | Used to greet the user |
lastName | No | Saved with the conversation |
email | No | Saved with the conversation |
This example uses Node.js with Express and the jsonwebtoken package:
import jwt from "jsonwebtoken";
const privateKey = process.env.VABLE_SIGNING_PRIVATE_KEY;
app.get("/api/vable-token", requireLogin, (req, res) => {
const token = jwt.sign(
{
email: req.user.email,
firstName: req.user.firstName,
lastName: req.user.lastName,
},
privateKey,
{
algorithm: "RS256",
subject: String(req.user.id),
expiresIn: "24h",
},
);
res.json({ token });
});
Anyone with the token can read what's inside it, so only include the claims listed above.
4. Pass the token to the SDK
Fetch the token after the user signs in, then pass it to the Vable component as accessToken. The widget works anonymously until the token arrives.
React
import { useEffect, useState } from "react";
import { Vable } from "@vableai/react/init";
function App() {
const [accessToken, setAccessToken] = useState<string>();
useEffect(() => {
fetch("/api/vable-token", { credentials: "include" })
.then((response) => (response.ok ? response.json() : null))
.then((data) => setAccessToken(data?.token));
}, []);
return (
<Vable
publicKey="<replace_with_public_key>"
accessToken={accessToken}
routes={routes}
>
{/* your app */}
</Vable>
);
}
Vue
<template>
<Vable
public-key="<replace_with_public_key>"
:access-token="accessToken"
:routes="routes"
>
<RouterView />
</Vable>
</template>
<script setup>
import { onMounted, ref } from "vue";
import { Vable } from "@vableai/vue/init";
import { routes } from "./router";
const accessToken = ref();
onMounted(async () => {
const response = await fetch("/api/vable-token", { credentials: "include" });
if (response.ok) {
accessToken.value = (await response.json()).token;
}
});
</script>
Token expiry and sign-out
VableAI checks the token each time a conversation starts or resumes. A conversation that's already running isn't interrupted when the token expires.
Renewing a token: Fetch a new token before the old one expires and update accessToken. The SDK sends the new value to the widget.
Signing out: Reload the page when a user signs out. Clearing accessToken doesn't remove the token from a widget that's already loaded.
Troubleshooting
An invalid token stops the conversation from starting. VableAI doesn't fall back to an anonymous conversation.
flowchart TD
A[Conversation starts] --> B{Token passed?}
B -- No --> C[Anonymous conversation]
B -- Yes --> D{Signing key uploaded?}
D -- No --> X[Conversation fails to start]
D -- Yes --> E{Signature valid and token not expired?}
E -- No --> X
E -- Yes --> F{Has a sub claim?}
F -- No --> X
F -- Yes --> G[Conversation linked to the user]
| Problem | Likely cause |
|---|---|
| The conversation doesn't start | No signing key is uploaded for this project, or the token expired |
| The conversation doesn't start after you replaced the signing key | The server is still signing with the old private key |
| The conversation doesn't start with a valid-looking token | The token isn't signed with RS256, or it has no sub claim |
| The widget doesn't greet the user by name | The token has no firstName claim |