Client & Authentication

Client & Auth

Best Practices

  • Use singleton pattern - Always use getOrCreateInstance() to avoid multiple client instances
  • Use tokenProvider - Prefer tokenProvider over static tokens for automatic refresh on expiry
  • Clean up on logout - Call disconnectUser() when the user logs out
  • Server-side token generation - Never generate tokens client-side in production

Set up the video client before joining a call. Basic example:

import { StreamVideoClient, User } from "@stream-io/video-react-native-sdk";

const user: User = { id: "sara" };
const apiKey = "my-stream-api-key";
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

const client = StreamVideoClient.getOrCreateInstance({ apiKey, token, user });
  • API Key - Found in your dashboard
  • User types - Authenticated, anonymous, or guest
  • Custom data - Store additional data on the user object as needed

Initialize the client when your application loads and use a context provider to make it available throughout your app.

Generating a token

Generate tokens server-side using our server SDKs. Integrate token generation into your login/registration flow. Tokens authenticate users and control call access.

Here are credentials to try out the app with:

PropertyValue
API Keymmhfdzb5evj2
TokeneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3Byb250by5nZXRzdHJlYW0uaW8iLCJzdWIiOiJ1c2VyL1lpZWxkaW5nX1dhc3RlIiwidXNlcl9pZCI6IllpZWxkaW5nX1dhc3RlIiwidmFsaWRpdHlfaW5fc2Vjb25kcyI6NjA0ODAwLCJpYXQiOjE3NzAzOTUwOTIsImV4cCI6MTc3MDk5OTg5Mn0.nnKYTmZCR7BwAopJ6GZDoY38ovsaazdV8MoKLIUZwhQ
User IDYielding_Waste
Call IDS6igOfKiOZMzn6S0bCtc6

For development purposes, you can use our Token Generator.

Different types of users

  • Authenticated users - Users with an account on your app
  • Guest users - Temporary accounts with a name and image for joining calls
  • Anonymous users - Unauthenticated users, typically for watching livestreams

Guest users

Guest user setup:

import { StreamVideoClient, User } from "@stream-io/video-react-native-sdk";

const user: User = { id: "jack-guest", type: "guest" };
const apiKey = "my-stream-api-key";
const client = StreamVideoClient.getOrCreateInstance({ apiKey, user });

Anonymous users

Anonymous user setup:

import { StreamVideoClient, User } from "@stream-io/video-react-native-sdk";

const user: User = { type: "anonymous" };
const apiKey = "my-stream-api-key";
const client = StreamVideoClient.getOrCreateInstance({ apiKey, user });

Anonymous users don't establish a WebSocket connection and won't receive events. They can only watch livestreams or join calls.

The anonymous user token must include the call_cids field - an array of call CIDs the user can join.

Example JWT token payload for anonymous users:

{
  "iss": "@stream-io/dashboard",
  "iat": 1726406693,
  "exp": 1726493093,
  "user_id": "!anon",
  "role": "viewer",
  "call_cids": ["livestream:123"]
}

Connecting a user and error handling

Connect users in two ways:

Automatic connection - when creating the client:

const client = new StreamVideoClient({
  apiKey,
  user,
  token,
  options: {
    maxConnectUserRetries: 3,
    onConnectUserError: (err: Error, allErrors: Error[]) => {
      console.error("Failed to connect user", err, allErrors);
      // handle the connect error, i.e. ask the user to retry
      // later when they have better connection or show an error message
    },
  },
});

Manual connection - using client.connectUser():

const client = new StreamVideoClient({ apiKey });
try {
  await client.connectUser(user, token);
} catch (err) {
  console.error("Failed to connect user", err);
  // handle the connect error
}

Disconnecting a user

Discard a client instance or disconnect a user with client.disconnectUser():

await client.disconnectUser();

Client options

token or tokenProvider

Authenticate users with either a string token or a tokenProvider function returning Promise<string>.

When using a tokenProvider, the SDK will automatically execute it to refresh the token whenever the token expires.

import { StreamVideoClient, User } from "@stream-io/video-react-native-sdk";

const tokenProvider = async () => {
  const response = await fetch("/api/token");
  const data = await response.json();
  return data.token;
};

const user: User = { id: "sara" };
const apiKey = "my-stream-api-key";
const client = StreamVideoClient.getOrCreateInstance({
  apiKey,
  tokenProvider,
  user,
});

Reject incoming call when busy

Configure the client to auto-reject incoming calls when the user is already in a call by setting rejectCallWhenBusy to true.

import { StreamVideoClient } from "@stream-io/video-react-native-sdk";

const client = StreamVideoClient.getOrCreateInstance({
  apiKey,
  tokenProvider,
  user,
  options: { rejectCallWhenBusy: true },
});

The SDK plays a busy tone on the caller's side when the callee rejects due to being busy.

Logging

The SDK uses scoped logging. Each scope represents a module or group of modules.

Configure a sink and log level per scope. The SDK uses the default scope when no specific settings exist. A sink is a function that receives logs with level, message, and additional arguments.

Log levels by severity:

  • trace: 0
  • debug: 1
  • info: 2
  • warn: 3
  • error: 4

Setting log level to warn for event-dispatcher scope shows only errors and warnings for that scope.

Note that our SDK only logs errors that do not get re-thrown. Using our API (producing HTTP request) might result in errors which should be handled within individual integrations.

import { StreamVideoClient } from "@stream-io/video-react-native-sdk";

const client = StreamVideoClient.getOrCreateInstance({
  apiKey,
  token,
  user,
  options: {
    logOptions: {
      default: {
        level: "info",
      },
      coordinator: {
        level: "warn",
        sink: (logLevel, message, ...rest) => {
          switch (logLevel) {
            case "warn": {
              console.warn(message, ...rest);
              break;
            }
            case "error": {
              SuperLogger.error(message, ...rest);
            }
          }
        },
      },
    },
  },
});

Augment the default scope settings to avoid configuring each SDK scope individually.

import { videoLoggerSystem } from "@stream-io/video-client";

import SuperLogger from "./SuperLogger";

videoLoggerSystem.configureLoggers({
  default: {
    level: "info",
    sink: (logLevel, message, ...rest) => {
      SuperLogger[logLevel](message, ...rest);
    },
  },
});

Reset specific scopes to defaults at runtime:

import { videoLoggerSystem } from "@stream-io/video-client";

videoLoggerSystem.configureLoggers({
  "event-dispatcher": {
    level: null,
    sink: null,
  },
});

This resets only event-dispatcher; other scopes remain unchanged. To reset all defaults:

import { videoLoggerSystem } from "@stream-io/video-client";

videoLoggerSystem.restoreDefaults();

StreamVideo context provider

Use the StreamVideo context provider to make the SDK client available throughout your application. This example uses tokenProvider for server-side auth:

import { useEffect, useState } from "react";
import {
  StreamVideo,
  StreamVideoClient,
  User,
} from "@stream-io/video-react-native-sdk";

const apiKey = "my-stream-api-key";
const user: User = { id: "sara", name: "Sara" };

export const MyApp = () => {
  const [client, setClient] = useState<StreamVideoClient>();
  useEffect(() => {
    const tokenProvider = () => Promise.resolve("<token>");
    const myClient = StreamVideoClient.getOrCreateInstance({
      apiKey,
      user,
      tokenProvider,
    });
    setClient(myClient);
    return () => {
      myClient.disconnectUser();
      setClient(undefined);
    };
  }, []);

  if (!client) return null;

  return (
    <StreamVideo client={client}>
      <MyVideoApp />
    </StreamVideo>
  );
};