Autenticação
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.
- Seu plugin é o provedor de identidade. Ele renderiza a tela de login, se comunica com o provedor externo e decide quem é permitido entrar.
- 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.
Tudo nesta página precisa da permissão auth.gate, além da permissão users.register para criar o usuário autenticado e http.serve para renderizar o fluxo de login.
O que está atrás do gate
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. Com Armazenamento de Objetos ou CDN, as playlists são reescritas para URLs remotas absolutas e segmentos são buscados diretamente do bucket, então o gate nunca vê essas requisições. O gating ainda impede que um visitante anônimo descubra a lista de segmentos, mas um URL de segmento vazado ou compartilhado continua acessível. Stream protection + local distribution is airtight. Stream protection + Object Storage is good friction, not airtight.
Como funciona
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. Chamar seu motor embutido do plugin em cada uma delas derreteria o servidor, por isso o plugin é mantido fora do caminho quente:
| Quando | Custo | O que acontece |
|---|---|---|
| Every non-exempt request | Verificar a assinatura do cookie + vencimento | valid passes. Missing or invalid gets a redirect to login, or a 401 for anything that is not a GET or HEAD |
Página / carrega apenas | Chamada de motor opcional: onAuthCheck | re-validar com seu provedor, retorna ok / refresh / deny |
Seu plugin só executa o fluxo de login (infrequente, cerca de uma vez por sessão de espectador) e o opcional onAuthCheck por carregamento de página. O host Owncast gera e verifica um cookie de sessão assinado para que a verificação por requisição seja apenas assinatura e validade: sem consulta ao banco de dados, sem chamada ao 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. Seu plugin nunca vê ou define o token, então não pode forjar ou vazar um. (Esta é também a forma como o chat coleta a identidade do espectador automaticamente. Veja Identidade do Chat abaixo.)
Construindo um plugin de gate
Um plugin de gate é um plugin que serve HTTP com um fluxo de login. O loop de controle, por convenção, está enraizado no próprio namespace do seu plugin /plugins/\<sua-sigla>/:
Três partes fazem o trabalho:
- Registrar o usuário. Transforme a identidade externa em um verdadeiro usuário Owncast com
owncast.users.register. Passe umauthIdestável e com escopo do provedor (por exemplo,"github:583231"). O host o namespace usando sua sigla para que os plugins não possam colidir ou se disfarçar um ao outro. - Conceder a sessão. Chame
owncast.auth.grantSessioncom esseuserId. O host Owncast gera o cookie assinado e o anexa à resposta em trânsito. Isso só funciona dentro de um manipuladoronHttpRequest. - Redirecionar para casa. O host Owncast acrescenta um parâmetro de consulta
return_toquando redireciona um visitante não autenticado para sua tela de login e o higieniza para um caminho de mesma origem (para que não possa ser transformado em um redirecionamento aberto). Envie o espectador para lá após um login bem-sucedido.
Para desconectar um espectador, chame owncast.auth.endSession() e redirecione. Seu plugin ainda controla para onde (pode redirecionar para o logout do próprio provedor).
Revogação com onAuthCheck
As sessões são sem estado, então não há uma lista por requisição "este usuário ainda está autorizado". Isso colocaria o plugin de volta no caminho quente. Em vez disso, defina o manipulador opcional onAuthCheck. Ele dispara em cada carregamento da página / com a identidade do espectador resolvida e retorna ok, refresh (reemitir o cookie, opcionalmente com um novo TTL para expiração deslizante) ou deny (encerrar a sessão e voltar ao login). Um plugin respaldado por provedor verifica a associação aqui (organização ainda válida? conta não deletada?).
Como a verificação é feita apenas em /, um espectador que você revogar mantém funcional qualquer guia aberta até que eles atualizem ou o cookie expire. O TTL da sessão é o limite rígido para a revogação, então mantenha-o curto se a revogação rápida for importante.
Exemplo prático: um gate de senha compartilhada
O exemplo de plugin basic-auth é o gate mais simples possível: uma senha compartilhada, uma identidade "Convidado" compartilhada, sem provedor externo. Ele é enviado tanto em examples/js/basic-auth quanto em examples/python/basic-auth.
Seu manifesto declara as permissões e um único campo de configuração para a senha:
{
"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"
}
}
}
O manipulador renderiza um formulário de senha em /, verifica a senha enviada contra o valor configurado e, em caso de sucesso, registra a identidade compartilhada, concede uma sessão e redireciona de volta. onAuthCheck lê um flag revoked que pode ser ativado pelo administrador para expulsar todos em seu próximo carregamento de página. (O helper page() que constrói o formulário HTML é omitido abaixo por brevidade. Veja a fonte do exemplo.)
- 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()
Para um verdadeiro fluxo OAuth (CSRF state em storage.kv, uma troca de código via network.fetch, imposição de associação organizacional e uma URL de retorno construída a partir de owncast.server.info()), veja o exemplo github-auth no SDK.
Habilitando o gate
Declarar auth.gate não faz nada por si só. O gate é armado por habilitar o plugin através do ciclo normal de ativação/desativação no administrador. Desabilite-o e o gate cai instantaneamente.
- Apenas um plugin
auth.gatepode ser habilitado por vez. O Owncast se recusa a habilitar um segundo enquanto um já estiver ativo ("desabilite o outro primeiro"). - Configure antes de ativar. Um plugin pode ser instalado e configurado enquanto estiver desativado, e depois habilitado para entrar ao vivo. Use o formulário de configuração gerado automaticamente para credenciais como um ID de cliente OAuth e segredo.
Falhar em fechado
A postura do gate é desacoplada da saúde do seu plugin. Se o gate estiver armado, mas o plugin estiver indisponível (crash, falha ao carregar, erro, ou desativado automaticamente após falhas repetidas), o Owncast nega todo o tráfego de espectadores e serve uma página estática "autenticação temporariamente indisponível". Ele nunca se abre. O administrador está sempre acessível (as rotas de administrador usam a Autenticação Básica existente do Owncast e ignoram o gate), então você pode corrigir a configuração ou desabilitar o plugin. Sessões já válidas sobrevivem a uma queda, porque verificar um cookie não precisa de chamada ao 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.
O que contorna o gate
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.
Detalhes da sessão
- Stateless signed cookie named
owncast_session,HttpOnly,Secure(on HTTPS requests),SameSite=Lax,Path=/. Lax em vez de Strict porque o retorno do provedor é um redirecionamento de nível superior entre sites. 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. Como o TTL é a segurança contra revogação, é um verdadeiro botão de segurança. - O segredo de assinatura é responsabilidade do host Owncast. É gerado automaticamente na primeira utilização e persistido na configuração. Rotacioná-lo invalida todas as sessões (um botão de pânico). Os autores de plugins nunca o tocam, e está separado de qualquer segredo de cliente OAuth, que é uma preocupação da configuração do seu plugin.
Identidade do chat
Um login de gate produz automaticamente uma identidade de chat autenticada. 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. Nenhum token é enviado para o localStorage do navegador. The viewer signs in once and shows up in chat under that name.
Relativo
- Permissões:
auth.gate,users.register - APIs do Owncast:
users.register,auth.grantSession,auth.endSession - Eventos: o manipulador
onAuthCheck - Servindo HTTP: o modelo de requisição no qual o fluxo de login é construído
Improve this page
See something missing or incorrect? Edit this page and improve the documentation for everyone.
