Autorización de canales
Cualquier cliente que tenga la clave de la app puede suscribirse a canales públicos. Dos prefijos de nombre de canal requieren que tu backend autorice la suscripción. No necesitas configurar los canales por separado.
Un canal llamado private-… requiere que tu backend apruebe cada suscripción. Un canal llamado presence-… hace lo mismo y además asocia una identidad al suscriptor, de modo que todos en el canal pueden ver quién más está presente. Cualquier otro nombre es público.
Solo tu backend tiene el secreto de la app. El cliente pide a tu servidor que firme una suscripción específica, y el borde de Realtime verifica esa firma antes de aceptarla. Tu servidor decide si el solicitante puede suscribirse sin exponer el secreto al cliente.
Apunta el cliente a tu endpoint
Dale al cliente un authEndpoint en tu propio backend:
import { BirdRealtime } from "@messagebird/realtime";
const bird = new BirdRealtime({
appKey: "your-app-key",
region: "us1",
authEndpoint: "/bird/auth",
});
const room = bird.subscribe("presence-room-1");import BirdRealtime
let bird = BirdRealtime(options: .init(
appKey: "your-app-key",
region: "us1",
authEndpoint: URL(string: "https://your-backend.example.com/bird/auth")
))
let room = bird.subscribe("presence-room-1")import com.bird.realtime.BirdRealtime
import com.bird.realtime.BirdRealtimeOptions
val bird = BirdRealtime(
BirdRealtimeOptions(
appKey = "your-app-key",
region = "us1",
authEndpoint = "https://your-backend.example.com/bird/auth",
)
)
val room = bird.subscribe("presence-room-1")El cliente llama a este endpoint para cada suscripción privada o de presencia, incluidas las suscripciones restauradas después de una reconexión. La autorización aplica a una sola conexión porque la firma incluye su ID de conexión.
El cliente del navegador requiere un endpoint del mismo origen por defecto. Configura allowCrossOriginAuth: true para usar un authEndpoint de origen cruzado. El cliente del navegador envía las authHeaders configuradas solo a endpoints del mismo origen.
Qué recibe y devuelve tu endpoint
El cliente envía por POST JSON:
Ejemplo de código
{ "connection_id": "26896.319537", "channel_name": "presence-room-1" }Responde con la firma:
Ejemplo de código
{ "auth": "your-app-key:8f9a…" }Para un canal de presencia, devuelve también la identidad del miembro como una JSON de tipo string, la misma cadena que firmaste:
Ejemplo de código
{
"auth": "your-app-key:8f9a…",
"member_data": "{\"member_id\":\"u_42\",\"member_info\":{\"name\":\"Ada\"}}"
}member_id es la identidad que ven los demás miembros y el valor al que apunta la operación de desconexión. member_info son datos JSON opcionales que se entregan a cada miembro del canal. Tiene un límite de 1 KB, así que incluye solo datos de perfil pequeños y no sensibles.
Autoriza al solicitante en este endpoint usando su cookie de sesión o token bearer. Devuelve 403 Forbidden cuando el solicitante no deba unirse al canal. Para canales de presencia, asigna la identidad en la misma respuesta.
La cadena que firmas
Concatena con dos puntos, luego aplica HMAC-SHA256 con el secreto de la app y codifica en hexadecimal. Antepón al resultado la clave de la app y dos puntos.
| Tipo de canal | Cadena a firmar |
|---|---|
| private-… | <connection_id>:<channel_name> |
| private-encrypted-… | <connection_id>:<channel_name> |
| presence-… | <connection_id>:<channel_name>:<member_data> |
Para canales de presencia, firma la cadena member_data exacta que devuelves. Re-serializar el mismo objeto puede cambiar el orden de las claves o el espaciado e invalidar la firma.
Un canal cifrado se firma igual que uno privado, y su respuesta de autorización además devuelve la clave de descifrado del canal como shared_secret. El helper SDK la añade automáticamente; Canales cifrados cubre la derivación y el comportamiento del canal.
Cada SDK de servidor proporciona un helper authorizeChannel. Firma con las credenciales de app configuradas y devuelve el cuerpo de la respuesta sin hacer una solicitud de red. Para canales cifrados, el helper también añade shared_secret.
app.post("/bird/auth", async (req, res) => {
const { connection_id, channel_name } = req.body;
// Your own authorization decision goes here.
const user = getUserFromSession(req);
if (!user || !mayJoin(user, channel_name)) return res.sendStatus(403);
const memberData = channel_name.startsWith("presence-")
? JSON.stringify({ member_id: user.id, member_info: { name: user.name } })
: undefined;
res.json(
await bird.realtime.authorizeChannel({
connectionId: connection_id,
channelName: channel_name,
memberData,
}),
);
});import json
@app.post("/bird/auth")
def bird_auth():
body = request.get_json()
channel_name = body["channel_name"]
# Your own authorization decision goes here.
user = get_user_from_session()
if user is None or not may_join(user, channel_name):
abort(403)
member_data = None
if channel_name.startswith("presence-"):
member_data = json.dumps({"member_id": user.id, "member_info": {"name": user.name}})
return client.realtime.authorize_channel(
connection_id=body["connection_id"],
channel_name=channel_name,
member_data=member_data,
)func birdAuth(w http.ResponseWriter, r *http.Request) {
var body struct {
ConnectionID string `json:"connection_id"`
ChannelName string `json:"channel_name"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
// Your own authorization decision goes here.
user, ok := userFromSession(r)
if !ok || !mayJoin(user, body.ChannelName) {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
params := bird.RealtimeChannelAuthorizationParams{
ConnectionID: body.ConnectionID,
ChannelName: body.ChannelName,
}
if strings.HasPrefix(body.ChannelName, "presence-") {
memberData, _ := json.Marshal(map[string]any{
"member_id": user.ID,
"member_info": map[string]string{"name": user.Name},
})
params.MemberData = string(memberData)
}
auth, err := client.Realtime.AuthorizeChannel(params)
if err != nil {
http.Error(w, "authorization failed", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(auth)
}function birdAuth(string $connectionId, string $channelName, User $user): array
{
// Your own authorization decision goes here.
if (!mayJoin($user, $channelName)) {
http_response_code(403);
exit;
}
$memberData = null;
if (str_starts_with($channelName, 'presence-')) {
$memberData = json_encode(['member_id' => $user->id, 'member_info' => ['name' => $user->name]]);
}
return $bird->realtime->authorizeChannel($connectionId, $channelName, $memberData);
}El contrato de firma es el mismo en un lenguaje sin SDK: aplica HMAC-SHA256 a la cadena con el secreto de la app, codifícala en hexadecimal y anteponle la clave de la app y dos puntos.
Miembros y conexiones
Un miembro es una identidad, mientras que una conexión es un WebSocket abierto. Si alguien abre tu app en tres pestañas, un miembro tiene tres conexiones. member_added se dispara cuando la primera conexión se suscribe, y member_removed se dispara cuando la última se va. Las demás conexiones cambian el contador de conexiones del canal sin producir eventos de miembros.
Errores comunes
Una suscripción rechazada llega como un error de cliente. Revisa estas causas comunes:
- Firma inválida. La cadena que firmaste no coincide. Casi siempre se trata de un member_data re-serializado, o de una firma calculada sobre el nombre del canal sin su prefijo private- o presence-.
- Clave inválida. La clave en auth pertenece a otra app, o ha sido revocada. Rotar claves implica actualizar tanto el appKey del cliente como el secreto con el que firma tu endpoint.
- Datos de miembro faltantes. Llegó una suscripción de presencia sin member_data. Los canales de presencia no permiten unirse de forma anónima.
- Un 403 de tu propio endpoint. Tu decisión de autorización rechazó la solicitud, que es el resultado esperado para un usuario que no debe unirse.
Próximos pasos
- Envía tu primer evento en tiempo real es el tutorial de extremo a extremo sobre el que se apoya esta guía.
- Canales cifrados se apoyan en esta firma para también entregar a los suscriptores aprobados una clave de descifrado.
- Canales de presencia cubren la lista de miembros, los eventos de miembros y la lectura de presencia desde tu servidor.
- Cerrar conexiones de un miembro es la otra firma que calcula tu backend, y la que te permite cerrar las conexiones de un miembro.
- Webhooks y eventos cubre los eventos realtime.*, incluidos miembros que se unen y se van, en tu propio endpoint.
Recursos relacionados
Continúa con la documentación, guías y ejemplos sobre este tema. Los recursos están en inglés.