# Client & Authentication

## Client & Auth

Before joining a call, it is necessary to set up the video client. Here's a basic example:

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

const user: User = { id: "sara" };
const apiKey = "my-stream-api-key";
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
const client = new StreamVideoClient({ apiKey, token, user });
```

- The API Key can be found in your dashboard.
- The user can be either authenticated, anonymous or guest.
- Note: You can store custom data on the user object, if required.

Typically, you'll want to initialize the client when your application loads and store it in your application state to make it available to the rest of your application.

## Generating a token

Tokens need to be generated server side. You can use our [server side SDKs](/video/docs/api/authentication/) to quickly add support for this.
Typically, you integrate this into the part of your codebase where you login or register users.
The tokens provide a way to authenticate a user or give access to a specific set of calls.

<token-snippet app="meeting" style="credentials"></token-snippet>

<admonition type="note">

For development purposes, you can use our [Token Generator](/chat/docs/react/tokens-and-authentication/).

</admonition>

## Different types of users

- Authenticated users are users that have an account on your app.
- Guest users are temporary user accounts. You can use it to temporarily give someone a name and image when joining a call.
- Anonymous users are users that are not authenticated. It's common to use this for watching a livestream or similar where you aren't authenticated.

### Guest users

This example shows the client setup for a guest user:

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

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

### Anonymous users

And here's an example for an anonymous user

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

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

Anonymous users don't establish an active web socket connection, therefore they won't receive any events. They are just able to watch a livestream or join a call.

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

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

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 token for an anonymous user should contain the `call_cids` field, which is an array of the call `cid`'s that the user is allowed to join.

Here's an example JWT token payload for an anonymous user:

```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, you can provide 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.
If you use the `tokenProvider` the SDK will automatically call the provider whenever the token is expired.

```typescript
import { StreamVideoClient, User } from "@stream-io/video-client";

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

Or initialize with a static token:

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

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-client";

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](/video/docs/javascript/guides/camera-and-microphone/).

### Logging

The SDK uses pre-scoped logging mechanism for you to use. Each scope represents a specific module (or a group of modules) that produce logs.

You can set up a sink and a log level for each of these scopes individually. The mechanism will use `default` scope if logger cannot find settings for the accessed scope. A "sink" is a function which gets funneled logs an SDK code produces. A sink gets a log level, message and other values we pass down to it as arguments from the SDK.

Log levels are pre-defined and are ordered by severity as such:

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

For example; if you set a log level to a value `warn` for a scope `event-dispatcher`, you will only see errors and warnings generated by the SDK for that specific scope.

<admonition type="info">

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.

</admonition>

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

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

You can augment the settings of the `default` scope if you don't want to apply custom sinks/levels to all the SDK-defined scopes individually.

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

If you need to reset specific scopes during your application runtime to use defaults instead you can do so as follows:

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

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

This action will only reset the `event-dispatcher` scope and `api-client` scope will stay intact (considering previous examples). If you need to reset the defaults completely, you can do so as follows:

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

videoLoggerSystem.restoreDefaults();
```

### Sentry

Here is an example showing a basic [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,
  extraData?: any,
  tags?: string[],
) => {
  if (logLevel === "warn" || logLevel === "error") {
    Sentry.captureEvent({
      level: logLevelMapping.get(logLevel),
      extra: extraData,
    });
  }

  // Call the SDK's default log method
  logToConsole(logLevel, message, extraData, tags);
};
```


---

This page was last updated at 2026-06-11T09:47:29.837Z.

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