Passer au contenu principal

Authentification

An authentication gate plugin makes viewers sign in before reaching the resources selected by the server operator. The plugin supplies the login method, such as OAuth, a magic link, SAML, or a shared password. Owncast enforces the selected access mode.

  • Votre plugin est le fournisseur d'identité. Il rend l'écran de connexion, communique avec le fournisseur externe, et décide qui est autorisé à entrer.
  • The Owncast host is the gatekeeper and session authority. It owns the session cookie, enforces the selected access mode, and never puts your plugin in the per-request hot path.
Owncat informs youNécessite auth.gate

Tout sur cette page nécessite la permission auth.gate, plus users.register pour créer l'utilisateur authentifié et http.serve pour rendre le flux de connexion.

Ce qui est soumis à l'authentification

When an auth.gate plugin is enabled, the viewer page, chat, embeds, /api/config, and the rest of the public web surface require login. A short list of routes stays public in every mode, including Owncast's admin pages (they keep their own admin authentication, so an operator can always disable a broken gate), the instance logo, and the ActivityPub federation endpoints. See what bypasses the gate for the full list.

The operator selects one cumulative access mode on the plugin's Authentication tab:

Access modeEffect
Website only (default)The web interface requires sign-in. /hls/*, /api/status, and Owncast Directory listing stay public.
Website, video players, and other resourcesAlso gates Owncast-hosted /hls/*. Players such as VLC cannot complete the browser login. /api/status and directory listing stay public.
Website, video players, and server status requestsGates the web interface, Owncast-hosted /hls/*, and /api/status. Owncast Directory listing is disabled.

The modes are cumulative. There is no status-only mode that hides /api/status while leaving HLS public. The default protects the website without breaking existing players or uptime monitors.

Selecting either stream-protection mode blocks native players. VLC, QuickTime, mobile apps, and restreamers cannot complete a browser login or carry the session cookie. An Authorization header or query token does not bypass the gate.

A viewer with a valid session is always let through, regardless of the selected mode.

Owncat warns youDistribution de votre vidéo avec stockage externe (Stockage d'objets/CDN) mise en garde

When distributing your video stream directly from your server, stream protection is airtight: every byte flows through Owncast. Avec le Stockage d'objets ou le CDN, les listes de lecture sont réécrites en URL distantes absolues et les segments sont récupérés directement à partir du compartiment, donc la porte ne voit jamais ces demandes. La porte empêche toujours un visiteur anonyme de découvrir la liste des segments, mais une URL de segment fuitée ou partagée reste récupérable. Stream protection + local distribution is airtight. Stream protection + Object Storage is good friction, not airtight.

Comment ça fonctionne

Once the gate is armed, every non-exempt request is checked. Under stream protection that includes each HLS segment, which a live viewer pulls every few seconds. Appeler le moteur intégré de votre plugin à chacune de ces demandes ferait fondre le serveur, donc le plugin est tenu hors du chemin chaud :

QuandCoûtQue se passe-t-il ?
Every non-exempt requestVérifiez la signature du cookie + expirationvalid passes. Missing or invalid gets a redirect to login, or a 401 for anything that is not a GET or HEAD
La page / ne se charge queAppel facultatif du moteur : onAuthCheckre-vérifier auprès de votre fournisseur, renvoie ok / rafraîchir / refuser

Votre plugin exécute uniquement le flux de connexion (rare, environ une fois par session de spectateur) et le onAuthCheck facultatif par chargement de page. L'hôte Owncast crée et vérifie un cookie de session signé afin que la vérification par demande ne soit que par signature et expiration : pas de recherche en base de données, pas d'appel au plugin.

The cookie is a signed envelope carrying an Owncast access token plus a session expiry. The host mints a fresh access token for the user each time it grants a session. The Owncast host owns the cookie end to end: it reserves the cookie name (owncast_session), signs it with a host-held secret, and attaches it to the response. Votre plugin ne voit jamais ni ne définit le jeton, donc il ne peut pas en forger ou en divulguer un. (C'est aussi ainsi que le chat récupère automatiquement l'identité du spectateur. Voir Identité du chat ci-dessous.)

Création d'un plugin de porte

Un plugin de porte est un plugin servant HTTP avec un flux de connexion. La boucle de contrôle, par convention, est enracinée dans l'espace de noms de votre plugin /plugins/\<your-slug>/:

Trois éléments effectuent le travail :

  1. Inscrire l'utilisateur. Transformez l'identité externe en un véritable utilisateur Owncast avec owncast.users.register. Passez un authId stable, périmé par le fournisseur (ex. "github:583231"). L'hôte lui attribue par votre slug afin que les plugins ne puissent pas se heurter ou se falsifier.
  2. Accorder la session. Appelez owncast.auth.grantSession avec ce userId. L'hôte Owncast crée le cookie signé et l'attache à la réponse en vol. Cela ne fonctionne qu'à l'intérieur d'un gestionnaire onHttpRequest
  3. Redirigez vers l'accueil. L'hôte Owncast ajoute un paramètre de requête return_to lorsqu'il redirige un visiteur non authentifié vers votre écran de connexion, et le sanitise en un chemin de même origine (afin qu'il ne puisse pas être transformé en une redirection ouverte). Envoyez le spectateur là après une connexion réussie.

Pour déconnecter un spectateur, appelez owncast.auth.endSession() et redirigez. Votre plugin contrôle toujours où aller (il peut rediriger vers la déconnexion de son propre fournisseur).

Révocation avec onAuthCheck

Les sessions sont sans état, donc il n'y a pas de liste de "cet utilisateur est-il toujours autorisé" par requête. Cela remettrait le plugin sur le chemin chaud. Au lieu de cela, définissez le gestionnaire optionnel onAuthCheck. Il se déclenche à chaque chargement de page / avec l'identité du spectateur résolue, et renvoie ok, refresh (réémettre le cookie, éventuellement avec un nouveau TTL pour l'expiration glissante), ou deny (mettre fin à la session et renvoyer vers la connexion). Un plugin soutenu par le fournisseur vérifie à nouveau l'adhésion ici (organisation toujours valide ? compte non supprimé ?).

Parce que la vérification ne s'exécute que sur /, un spectateur que vous révoquez garde n'importe quel onglet ouvert fonctionnant jusqu'à ce qu'il recharge ou que le cookie expire. Le TTL de la session est la limite stricte pour la révocation, donc gardez-le court si une révocation rapide est importante.

Exemple de travail : une porte avec mot de passe partagé

Le plugin d'exemple basic-auth est la porte la plus simple possible : un mot de passe partagé, une seule identité "Invité" partagée, aucun fournisseur externe. Il est disponible dans examples/js/basic-auth et examples/python/basic-auth.

Son manifeste déclare les permissions et un seul champ de configuration pour le mot de passe :

{
"name": "Basic Auth",
"slug": "basic-auth",
"version": "0.1.0",
"permissions": ["auth.gate", "users.register", "http.serve", "storage.kv"],
"config": {
"password": {
"type": "string",
"default": "letmein",
"description": "Shared password viewers must enter to watch"
}
}
}

Le gestionnaire affiche un formulaire de mot de passe à /, vérifie le mot de passe soumis contre la valeur configurée, et, en cas de succès, enregistre l'identité partagée, accorde une session, et redirige en retour. onAuthCheck lit un drapeau révoqué modifiable par l'administrateur pour mettre tout le monde hors ligne lors de leur prochain chargement de page. (L'aide page() qui construit le formulaire HTML est omise ci-dessous pour plus de concision. Voir la source de l'exemple.)

const { definePlugin, owncast, authCheck } = require('@owncast/plugin-sdk');

module.exports = definePlugin({
onHttpRequest(req) {
const query = req.query || {};
const returnTo = query.return_to || '/';

if (req.method === 'GET' && req.path === '/') {
return {
status: 200,
headers: { 'content-type': 'text/html' },
body: page(returnTo),
};
}

if (req.path === '/login') {
const expected = owncast.config.get('password', 'letmein');
if ((query.password || '') !== expected) {
return {
status: 200,
headers: { 'content-type': 'text/html' },
body: page(returnTo, 'Incorrect password.'),
};
}
// Everyone who knows the password shares one authenticated identity.
const { userId } = owncast.users.register({
authId: 'shared',
displayName: 'Guest',
});
owncast.auth.grantSession({ userId });
return { status: 302, headers: { Location: returnTo } };
}

if (req.path === '/logout') {
owncast.auth.endSession();
return { status: 302, headers: { Location: '/' } };
}

// Admin-only revocation toggle. req.authenticated is true for admins only.
if (req.path === '/revoke' || req.path === '/unrevoke') {
if (!req.authenticated) return { status: 403, body: 'admin only' };
owncast.kv.set('revoked', req.path === '/revoke' ? '1' : '');
return {
status: 200,
body: req.path === '/revoke' ? 'revoked' : 'unrevoked',
};
}

return { status: 404, body: 'not found' };
},

// Re-validate on each page load. While revoked, end every session.
onAuthCheck() {
if (owncast.kv.get('revoked') === '1') return authCheck.deny('access has been revoked');
return authCheck.ok();
},
});

Pour un vrai flux OAuth (CSRF state dans storage.kv, un échange de code sur network.fetch, validation de l'adhésion à une organisation, et une URL de rappel construite à partir de owncast.server.info()), voir l'exemple github-auth dans le SDK.

Activation de la porte

Déclarer auth.gate ne fait rien en soi. La porte est armée par l’activation du plugin via le cycle normal d’activation/désactivation dans l’administration. Désactivez-le et la porte tombe instantanément.

  • Un seul plugin auth.gate peut être activé à la fois. Owncast refuse d'activer un second alors qu'un est déjà actif ("désactivez l'autre d'abord").
  • Configurez avant d’activer. Un plugin peut être installé et configuré tout en étant désactivé, puis activé pour être mis en ligne. Utilisez le formulaire de configuration auto-généré pour des identifiants comme un ID client OAuth et un secret.

Échouer avec une fermeture

La posture de la porte est découplée de la santé de votre plugin. Si la porte est armée mais que le plugin est indisponible (planté, échec de chargement, erreur ou désactivé automatiquement après des échecs répétés), Owncast ** interdit tout trafic de spectateurs ** et sert une page statique "authentification temporairement indisponible". Elle ne s'ouvre jamais. L'administrateur est toujours joignable (les routes d'administration utilisent l'authentification de base existante d'Owncast et contournent la porte) afin que vous puissiez corriger la configuration ou désactiver le plugin. Les sessions déjà valides survivent à une panne, car la vérification d'un cookie ne nécessite pas d'appel au plugin.

A gate that is enabled but not running is still a gate. No access-policy setting can turn a failing-closed gate into an open one.

Ce qui contourne la porte

The gate covers the otherwise-public surface. Routes that enforce their own credentials bypass it. The selected access mode also leaves some resources public.

Always exempt:

  • The active gate plugin's own namespace /plugins/\<your-slug>/* and its static assets, so the login screen remains reachable.
  • /admin/* and /api/admin/*, which use admin authentication.
  • External API routes under /api/integrations/, which validate their own Bearer tokens.
  • Static viewer assets needed to render the page. HTML entry points are still gated.
  • /api/yp, which the Owncast Directory fetches anonymously. The most restrictive mode disables directory listing and makes this endpoint return 404.
  • /logo and /logo/external, the instance logo, which the viewer shell and federation metadata both reference.
  • /federation/*, the ActivityPub protocol surface. Those handlers enforce Owncast's own federation and privacy settings.

Mode-dependent:

  • /hls/* stays public only in Website only mode.
  • /api/status stays public in Website only and Website, video players, and other resources modes.

Everything else is gated, including embeds and /api/config.

Détails de la session

  • Stateless signed cookie named owncast_session, HttpOnly, Secure (on HTTPS requests), SameSite=Lax, Path=/. Lax plutôt que Strict parce que le rappel du fournisseur est une redirection en haut niveau à travers le site. The host owns the name: a plugin that tries to set it in its own response has that header stripped.
  • TTL is set by your plugin when it calls grantSession({ ttl }), defaulting to 24 hours and capped at 30 days. A sliding refresh is available through onAuthCheck's refresh verdict. Parce que le TTL est le dernier recours pour la révocation, c'est un vrai moyen de sécurité.
  • Le secret de signature est la responsabilité de l'hôte Owncast. Il est auto-généré lors de la première utilisation et conservé dans la configuration. Le fait de le faire tourner invalide chaque session (un bouton de panique). Les auteurs de plugins ne le touchent jamais, et il est séparé de tout secret client OAuth, qui concerne la configuration de votre plugin.

Identité du chat

Une connexion par porte produit automatiquement une identité de chat authentifiée. Because users.register creates or links a real Owncast user (marked authenticated, with the display name you passed, or a generated one if you passed none) and the session cookie carries an access token for that user, chat reads the identity straight from the cookie: when /ws (or a chat REST call) arrives with no ?accessToken= query parameter, it falls back to the access token in the gate cookie. Aucun jeton n'est jamais transporté dans le localStorage du navigateur. The viewer signs in once and shows up in chat under that name.

Lié


Improve this page

See something missing or incorrect? Edit the English version of this page or help improve translations.

Contributors to this documentation