Set up authentication
AI Transport authenticates through your existing auth: your server validates the user and signs an Ably token, and the browser's Ably client fetches it through `authCallback`and refreshes it before expiry.
This page is the practical setup. For the conceptual model (the three auth layers, capabilities, token lifecycle, cancel authorisation), see the authentication concept.
Sign tokens on the server
Create an endpoint that authenticates the user and returns a short-lived Ably JWT with the capabilities AI Transport needs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import jwt from 'jsonwebtoken';
const [keyName, keySecret] = process.env.ABLY_API_KEY.split(':');
export async function GET(req) {
const userId = await authenticateUser(req);
const ablyJwt = jwt.sign(
{
'x-ably-capability': JSON.stringify({
[`conversations:${userId}`]: ['publish', 'subscribe', 'history'],
}),
'x-ably-clientId': userId,
},
keySecret,
{ algorithm: 'HS256', keyid: keyName, expiresIn: '1h' },
);
return new Response(ablyJwt, { headers: { 'Content-Type': 'text/plain' } });
}Scope the capability to the channel namespace the user is allowed to access, conversations:${userId} here. The x-ably-clientId claim binds the token to a specific user identity that the Ably service verifies on every publish.
Fetch tokens from the client
Construct an Ably Realtime client with authCallback. The SDK calls it on first auth and again whenever a refresh is needed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import * as Ably from 'ably';
const realtimeClient = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
const response = await fetch('/api/auth/token', { credentials: 'include' });
if (!response.ok) throw new Error('Auth failed');
const jwt = await response.text();
callback(null, jwt);
} catch (error) {
callback(error, null);
}
},
});Wire it into the React provider stack
In a React app, hold the client in state, wrap the tree in AblyProvider, then in ClientSessionProvider for the channel:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
'use client'
import { useEffect, useState } from 'react';
import * as Ably from 'ably';
import { AblyProvider } from 'ably/react';
import { ClientSessionProvider, useClientSession } from '@ably/ai-transport/react';
import { UIMessageCodec } from '@ably/ai-transport/vercel';
export function Providers({ children }) {
const [client, setClient] = useState(null);
useEffect(() => {
const ably = new Ably.Realtime({
authCallback: async (_tokenParams, callback) => {
try {
const response = await fetch('/api/auth/token', { credentials: 'include' });
callback(null, await response.text());
} catch (err) {
callback(err instanceof Error ? err.message : String(err), null);
}
},
});
setClient(ably);
return () => ably.close();
}, []);
if (!client) return null;
return <AblyProvider client={client}>{children}</AblyProvider>;
}
function App({ conversationId }) {
return (
<ClientSessionProvider channelName={conversationId} codec={UIMessageCodec}>
<Chat />
</ClientSessionProvider>
);
}
function Chat() {
const { session, sessionError } = useClientSession();
// ...
}Authenticate the agent POST
The application's POST that wakes the agent is a separate HTTP request from the channel auth. Authenticate it however you normally do (session cookie, bearer token, signed header) when you call fetch on the core flow:
1
2
3
4
5
6
7
8
9
10
11
async function wakeAgent(run) {
await fetch('/api/chat', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${await getAccessToken()}`,
},
body: JSON.stringify(run.toInvocation().toJSON()),
});
}For the Vercel flow, ChatTransportProvider accepts a credentials prop and a chatOptions.prepareSendMessagesRequest hook that returns { body?, headers? } per request. Use the hook to attach auth headers to every invocation POST it makes for you.
Read next
- Authentication concept: the three auth layers, capabilities, and token lifecycle.
- Enable channel rules: the one-time namespace configuration AI Transport requires.
- Core SDK getting started: build a chat app on the auth set up here.
- Vercel AI SDK getting started: build a chat app using the Vercel wrapper.