Auth
Every request to Stream Agents is authenticated in one of two ways:
| Side | Runs in | Holds | Can reach |
|---|---|---|---|
| Server side | Your server, workers, scripts | API key and API secret | Everything in your Stream app |
| Client side | The browser, Android, iOS and Flutter | API key and a user token | That user's own sessions |
The API key is public. Your server uses the secret to mint a token for each signed-in user, and the app connects with the key and that token.
The secret is not public: never ship it in an app or a web page.
Server side
On your server, set your key and secret in the environment:
export STREAM_API_KEY=your_api_key
export STREAM_API_SECRET=your_api_secretThe client reads them, so it needs no arguments:
import { Client } from "@stream-io/stream-agents";
const client = new Client();from vision_agents.plugins import stream
client = stream.Client()require "getstream/vision_agents"
client = GetStream::VisionAgents::Client.newuse GetStream\VisionAgents\Client;
$client = new Client();api, err := client.New(stream.Backend{})
if err != nil {
log.Fatal(err)
}using GetStream.VisionAgents;
using var client = new VisionAgentsClient();use vision_agents::Client;
let client = Client::from_env()?;The SDK signs a short-lived server token with the secret on every request. Your server can configure and sync agents, and read and manage every session in your Stream app.
Client side
An app holds only the public API key. Once the user has signed in, hand the SDK a token your server minted for them:
import { Client } from "@stream-io/stream-agents";
const client = new Client({ apiKey: "your_api_key" });
await client.setUser("jlahey", () =>
fetch("/api/agent-token").then((response) => response.text()),
);val agents = StreamAgents(apiKey = "your_api_key")
agents.setUser(User(id = "jlahey")) { yourBackend.agentToken() }let agents = StreamAgents(apiKey: "your_api_key")
agents.setUser(User(id: "jlahey")) { try await yourBackend.agentToken() }final agents = VisionAgents(
apiKey: 'your_api_key',
userId: 'jlahey',
token: () => http.read(Uri.parse('https://your-app.example.com/api/agent-token')),
);The token can be a string, or a function that fetches one from your server. The Kotlin and Swift SDKs ask the function once and keep the token, then ask again when the router refuses it with a 401 and send the request once more. The JavaScript SDK does the same in releases after 0.3.1, where it also asks again shortly before the token's exp and when a session's socket is refused; 0.3.1 and earlier ask the function before every request.
When the user signs out, call clearUser(): the SDK forgets them and their token, and asks for a user again before its next request.
A client only reaches its signed-in user's own sessions. It can open, watch, ask, search, fork, rewind, rename, close and delete them. Configuring agents, syncing, and reading call records are server-side only. Each page lists what a client may call, such as Sessions.
Guests
A guest lets somebody talk to an agent before they sign up. The app asks for one with the public API key alone, and acts as it with setUser(guest), which takes the token the guest was minted with. In JavaScript:
Not documented for Kotlin yet. Showing JavaScript.
Not documented for Swift yet. Showing JavaScript.
Not documented for Flutter yet. Showing JavaScript.
import { Client } from "@stream-io/stream-agents";
const client = new Client({ apiKey: "your_api_key" });
// Remembered in a cookie, else localStorage, so a reload is the same guest with the same
// conversations. An expired guest is refreshed rather than replaced.
const guest = await client.guestUser({ name: "Guest" });
await client.setUser(guest);
const session = await client.agent("support").sessions.create();guestUser({ fresh: true }) starts over as a new guest, for a "not me" button. When the guest signs up or signs in, your server moves their conversations onto the account, and the app forgets the guest and sets the real user:
Not documented for Python yet. Showing Node.js.
Not documented for Ruby yet. Showing Node.js.
Not documented for PHP yet. Showing Node.js.
Not documented for Go yet. Showing Node.js.
Not documented for .NET yet. Showing Node.js.
Not documented for Rust yet. Showing Node.js.
// On your backend, with STREAM_API_KEY and STREAM_API_SECRET in the environment,
// once it has authenticated the account the guest turned out to be.
import { Client } from "@stream-io/stream-agents";
const server = new Client();
await server.claimGuestUser(guestId, "jlahey");In JavaScript, client.forgetGuest() clears the remembered guest before setUser names the account. Claiming is server-side only: a client able to claim could take anybody's conversations by guessing a guest id. A Stream app that has turned guests off refuses guestUser with a 403, from a client and from your server alike. A router that admits no anonymous callers refuses only the client: guestUser throws a ConfigurationError, and your server mints the guest with guestUser and hands it to the client for setUser.
Generating a token
For testing, mint one with the CLI:
getstream token jlahey --ttl 2hIn production, your server mints the token after it has signed the user in, and returns it to the app. A token is a JWT signed with your API secret (HS256), naming the user in user_id.
A token must carry an expiry: a token without exp is refused.
Stream's server SDKs mint one for you:
Not documented for Ruby yet. Showing Node.js.
Not documented for PHP yet. Showing Node.js.
Not documented for .NET yet. Showing Node.js.
Not documented for Rust yet. Showing Node.js.
import { StreamClient } from "@stream-io/node-sdk";
const stream = new StreamClient(
process.env.STREAM_API_KEY!,
process.env.STREAM_API_SECRET!,
);
const token = stream.generateUserToken({
user_id: "jlahey",
validity_in_seconds: 3600,
});from getstream import Stream
stream = Stream(api_key=os.environ["STREAM_API_KEY"], api_secret=os.environ["STREAM_API_SECRET"])
token = stream.create_token("jlahey", expiration=3600)stream, err := getstream.NewClient(os.Getenv("STREAM_API_KEY"), os.Getenv("STREAM_API_SECRET"))
if err != nil {
log.Fatal(err)
}
token, err := stream.CreateToken("jlahey", getstream.WithExpiration(time.Hour))The same token signs that user in to Stream Chat and Video, so one endpoint on your server serves all three.