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.eyJpc3MiOiJodHRwczovL3Byb250by5nZXRzdHJlYW0uaW8iLCJzdWIiOiJ1c2VyL01vb3JlZF9Ecml2ZSIsInVzZXJfaWQiOiJNb29yZWRfRHJpdmUiLCJ2YWxpZGl0eV9pbl9zZWNvbmRzIjo2MDQ4MDAsImlhdCI6MTc3MjI4MTEzNiwiZXhwIjoxNzcyODg1OTM2fQ.oJESVHeT56OZTMcxUx2vccf2tgcnxjDv2bSb9vk8D0M
User IDMoored_Drive
Call IDfVgpLb4jbFIM5AGNgWIoh

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

For authenticated users (default user.type), pass either token or tokenProvider. For guest users, do not pass token or tokenProvider. For anonymous users, token and tokenProvider are optional.

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.

You can also provide a call-scoped token (or token provider) for anonymous users:

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

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

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

For authenticated users, pass either a string token or a tokenProvider function returning Promise<string>. You can also provide both together. Anonymous users can also pass token or tokenProvider when needed.

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,
});

Or initialize with a static token:

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

const user: User = { id: "sara" };
const apiKey = "my-stream-api-key";
const token = "<token-from-your-server>";
const client = StreamVideoClient.getOrCreateInstance({
  apiKey,
  user,
  token,
});

When both are provided, the SDK uses token first and then tokenProvider for subsequent token refreshes:

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

const user: User = { id: "sara" };
const apiKey = "my-stream-api-key";
const token = "<token-from-your-server>";
const tokenProvider = async () => fetchTokenFromYourServer(user.id);
const client = StreamVideoClient.getOrCreateInstance({
  apiKey,
  user,
  token,
  tokenProvider,
});

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>
  );
};