# Requiring authorized connections

The app key is public. Any client that can load your page can open a connection with it and subscribe to your public channels, which is exactly what makes public channels convenient and also what makes a copied key useful to someone who is not your user.

Turning on **authorized connections** closes that gap. With the setting on, a new connection has to prove itself shortly after it opens, and the edge closes it if it does not.

## Turn it on

Toggle **Authorized connections** for the app on the [**Realtime → Apps**](https://bird.com/dashboard/w/realtime/apps) page, or set `authorized_connections` to `true` on the app through the Realtime API. It applies to the whole app, so every connection made with any of its keys is subject to it.

Do this before you ship the client change, or the other way round if you prefer, but not both at once in production: a client that does not authorize will be disconnected the moment the setting lands.

## What counts as authorized

A new connection starts out unauthorized. It authorizes the first time it does something that involves your backend's signature, and there are two ways that happens:

- **A private or presence subscription succeeds.** The client posts the connection id to your `authEndpoint`, your backend signs it, and the edge verifies the signature. A subscription that gets through is proof enough. See [Authorizing channels](/docs/guides/realtime/authorizing-channels).
- **`signin()` succeeds.** The client posts the connection id to your `memberAuthEndpoint` and gets a signed member identity back. See [Terminating member connections](/docs/guides/realtime/terminating-member-connections), which covers signin in full.

Both routes prove the same thing: something holding the app secret vouched for this connection. Subscribing to a public channel proves nothing and does not authorize anything.

## The public-channel-only case

This is the part that changes real code. A client that only ever subscribes to public channels never touches your backend, so with the setting on it will be closed. Signing in is the fix, and it works without subscribing to anything private:

```typescript
import { BirdRealtime } from "@messagebird/realtime";

const bird = new BirdRealtime({
  appKey: "your-app-key",
  region: "us1",
  memberAuthEndpoint: "/bird/auth/member",
});

await bird.signin();

const status = bird.subscribe("build-status");
status.bind("build-finished", (data) => render(data));
```

Call `signin()` once. The identity belongs to the connection rather than the page, so the client signs in again on every reconnect, and each new connection authorizes itself the same way. Your member auth endpoint is where the real decision lives: return a `403` for a caller you do not recognize and that connection never authorizes.

Apps that already subscribe to a private or presence channel on load usually need no client change at all, since that subscription authorizes the connection on its own.

## What the client sees when it does not authorize

The edge closes the connection with code `4009` and the reason `Connection not authorized within timeout`. That code is in the band the browser client refuses to retry, so it does not reconnect: the state goes to `failed` and stays there.

```typescript
bird.connection.bind("error", ({ code, message }) => {
  if (code === 4009) console.warn(message);
});
```

Two situations produce `4009`, and the reason text is the only thing that separates them. The other one is a member whose connections your backend terminated deliberately, covered in [Terminating member connections](/docs/guides/realtime/terminating-member-connections). If your app reacts to `4009` at all, read the reason before deciding whether to send someone to a login screen or a signed-out state. [Connection lifecycle and reconnection](/docs/guides/realtime/connection-lifecycle) has the full close-code policy.

An unauthorized connection is also not counted as a connection for the app while it waits, so short-lived unauthorized sockets do not show up in your metrics or eat into the connection limit.

## What it does not do

Requiring authorized connections raises the bar on holding a connection open. It is not a substitute for the checks in your endpoints, and it changes nothing about who may read a channel:

- A public channel stays public to every authorized connection. If the events belong to one customer, use a [private channel](/docs/guides/realtime/private-channels) and check the channel name in your endpoint.
- Anyone your own auth endpoints sign for is authorized, so a permissive endpoint hands out authorization as freely as the app key did.

## Next steps

- [Authorizing channels](/docs/guides/realtime/authorizing-channels) is the signature your backend computes for private and presence subscriptions.
- [Private channels](/docs/guides/realtime/private-channels) are the right tool when the events themselves belong to somebody.
- [Terminating member connections](/docs/guides/realtime/terminating-member-connections) covers `signin()` and the other use of `4009`.
- [Connection lifecycle and reconnection](/docs/guides/realtime/connection-lifecycle) explains why `4009` is terminal.