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.
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 mode | Effect |
|---|---|
| Website only (default) | The web interface requires sign-in. /hls/*, /api/status, and Owncast Directory listing stay public. |
| Website, video players, and other resources | Also 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 requests | Gates 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.
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 :
| Quand | Coût | Que se passe-t-il ? |
|---|---|---|
| Every non-exempt request | Vérifiez la signature du cookie + expiration | valid 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 que | Appel facultatif du moteur : onAuthCheck | re-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 :
- Inscrire l'utilisateur. Transformez l'identité externe en un véritable utilisateur Owncast avec
owncast.users.register. Passez unauthIdstable, 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. - Accorder la session. Appelez
owncast.auth.grantSessionavec ceuserId. 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 gestionnaireonHttpRequest - Redirigez vers l'accueil. L'hôte Owncast ajoute un paramètre de requête
return_tolorsqu'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.)
- JavaScript
- Python
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();
},
});
from owncast_plugin import plugin, owncast, auth_check
@plugin.get("/")
def login_form(req):
return_to = (req.raw.get("query") or {}).get("return_to") or "/"
return {"status": 200, "headers": {"content-type": "text/html"}, "body": page(return_to)}
@plugin.get("/login")
def login(req):
query = req.raw.get("query") or {}
return_to = query.get("return_to") or "/"
expected = owncast.config.get("password", "letmein")
if (query.get("password") or "") != expected:
return {"status": 200, "headers": {"content-type": "text/html"},
"body": page(return_to, "Incorrect password.")}
# Everyone who knows the password shares one authenticated identity.
result = owncast.users.register("shared", display_name="Guest")
owncast.auth.grant_session(result.user_id)
return {"status": 302, "headers": {"Location": return_to}}
@plugin.get("/logout")
def logout(req):
owncast.auth.end_session()
return {"status": 302, "headers": {"Location": "/"}}
@plugin.get("/revoke")
def revoke(req):
if not req.authenticated: # true for admin requests only
return {"status": 403, "body": "admin only"}
owncast.kv.set("revoked", "1")
return {"status": 200, "body": "revoked"}
@plugin.on_auth_check
def check(_req):
# Re-validate on each page load. While revoked, end every session.
if owncast.kv.get("revoked") == "1":
return auth_check.deny("access has been revoked")
return auth_check.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.gatepeut ê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 return404./logoand/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/statusstays 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 throughonAuthCheck'srefreshverdict. 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é
- Autorisations:
auth.gate,users.register - API d'Owncast:
users.register,auth.grantSession,auth.endSession - Événements: le gestionnaire
onAuthCheck - Servir HTTP: le modèle de requête sur lequel repose le flux de connexion
Improve this page
See something missing or incorrect? Edit the English version of this page or help improve translations.
