# Client & Authentication

Set up `StreamVideoClient` to connect users and join calls.

## Best Practices

- Create `StreamVideoClient` once at app root; use `tokenProvider` for auto-refresh.
- Always clean up with `client.disconnectUser()` in useEffect return.
- Use short-lived tokens with a `tokenProvider` for production.
- Handle connection errors with try/catch around `connectUser()`.

## Basic setup

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

const user: User = { id: "sara" };
const apiKey = "my-stream-api-key"; // from beta.dashboard.getstream.io
const tokenProvider = async () => fetchTokenFromYourServer(user.id);
const client = new StreamVideoClient({ apiKey, user, tokenProvider });
```

- The API Key can be found in your [dashboard](https://getstream.io/signin/?product=video).
- The user can be either authenticated, anonymous or guest.
- You can store custom data on the user object if required.

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

## Generating a token

Tokens must be generated server-side. Use our [server-side SDKs](https://getstream.io/docs/platform/authentication/) to add support for this - typically integrated with your login/registration flow.

> Interactive token generator: available on the web version of this page.

>
> **Note:** For development purposes, you can use our [Token Generator](https://getstream.io/chat/docs/react/tokens-and-authentication/).
>

## User types

- **Authenticated users** - users with an account in your app.
- **Guest users** - temporary accounts with a name and image.
- **Anonymous users** - unauthenticated users (common for livestream viewers).

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

Example setup for a guest user:

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

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

### Anonymous users

Example for an anonymous user:

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

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

Anonymous users don't establish a WebSocket connection, so they 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:

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

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

The token for anonymous users should include `call_cids` - an array of call CIDs they're allowed to join:

```json
{
  "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:

```typescript
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()`:

```ts
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()`:

```ts
await client.disconnectUser();
```

## Client options

### `token` or `tokenProvider`

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

> **Info:** When using `tokenProvider`, the SDK automatically refreshes expired tokens.

```ts
import { StreamVideoClient, User } from "@stream-io/video-react-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 = new StreamVideoClient({ apiKey, tokenProvider, user });
```

>
> **Warning:** Your `tokenProvider` is responsible for returning a valid token. The SDK retries
> when Stream rejects the current token as expired, but it does **not** retry
> failures coming from your own token service. If your `tokenProvider` rejects, the
> SDK stops that reconnect attempt because it cannot establish an authenticated
> connection.
>
> Because the SDK awaits `tokenProvider` and resumes reconnecting as soon as it
> resolves with a valid token, put your own retry/backoff policy inside the
> `tokenProvider` for transient failures and only
> reject for errors that retrying cannot resolve.
>

```ts
const tokenProvider = async () => {
  const maxAttempts = 5;

  for (let attempt = 0; ; attempt++) {
    try {
      const response = await fetch("/api/token");
      if (!response.ok)
        throw new Error(`Token request failed: ${response.status}`);
      const data = await response.json();
      return data.token;
    } catch (error) {
      // Give up on errors that retrying won't fix.
      if (attempt >= maxAttempts - 1) throw error;

      // Exponential backoff before the next attempt.
      const delay = Math.min(1000 * 2 ** attempt, 30_000);
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
};
```

Or initialize with a static token:

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

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

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

```ts
import { StreamVideoClient, User } from "@stream-io/video-react-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 = new StreamVideoClient({ apiKey, user, token, tokenProvider });
```

### Device persistence

Device preferences are persisted by default on web. Configure or disable this with `options.devicePersistence` when creating the client. See the [Camera & Microphone guide](https://getstream.io/video/docs/react/v2/guides/camera-and-microphone/).

### Reject incoming call when busy

Auto-reject incoming ringing calls when the user is already in a call:

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

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

### Logging

The SDK uses scoped logging with configurable levels per module. Log levels ordered by severity:

| Level | Severity |
| ----- | -------- |
| trace | 0        |
| debug | 1        |
| info  | 2        |
| warn  | 3        |
| error | 4        |

> **Info:** The SDK only logs errors that aren't re-thrown. API errors should be handled in your integration code.

```ts
import { StreamVideoClient, Logger } from "@stream-io/video-react-sdk";

const client = new StreamVideoClient({
  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);
            }
          }
        },
      },
    },
  },
});
```

Configure the default scope to apply settings globally:

```ts
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:

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

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

Reset all scopes to defaults:

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

videoLoggerSystem.restoreDefaults();
```

### Sentry integration

Example [Sentry](https://sentry.io/welcome/) integration:

```ts
import { LogLevel, Sink, logToConsole } from "@stream-io/video-client";
import * as Sentry from "@sentry/nextjs";

const logLevelMapping = new Map<LogLevel, Sentry.SeverityLevel>();
logLevelMapping.set("debug", "debug");
logLevelMapping.set("info", "info");
logLevelMapping.set("warn", "warning");
logLevelMapping.set("error", "error");

export const customSentryLogger: Sink = (
  logLevel: LogLevel,
  message: string,
  ...args: unknown[]
) => {
  if (logLevel === "warn" || logLevel === "error") {
    Sentry.captureEvent({
      level: logLevelMapping.get(logLevel),
      extra: args,
    });
  }

  // Call the SDK's default log method
  logToConsole(logLevel, message, { data: "some data" });
};
```

## StreamVideo context provider

The `StreamVideo` provider makes the client available throughout your app:

```tsx
import { useEffect, useState } from "react";
import {
  StreamVideo,
  StreamVideoClient,
  User,
} from "@stream-io/video-react-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 = new StreamVideoClient({ apiKey, user, tokenProvider });
    setClient(myClient);
    return () => {
      myClient.disconnectUser();
      setClient(undefined);
    };
  }, []);

  if (!client) return null;

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

---

For the most recent version of this documentation, visit [https://getstream.io/video/docs/react/v2/guides/client-auth/](https://getstream.io/video/docs/react/v2/guides/client-auth/).