跳至主要内容

身份验证

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.

  • 你的插件是身份提供者。 它呈现登录屏幕,和外部提供者交互,并决定谁被允许进入。
  • 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 you需要auth.gate

此页面上的所有内容都需要auth.gate权限,以及users.register以创建经过身份验证的用户和http.serve以呈现登录流程。

什么被门控

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 you使用外部存储(对象存储/CDN)分发视频的注意事项

When distributing your video stream directly from your server, stream protection is airtight: every byte flows through Owncast. 使用对象存储或CDN,播放列表被重写为绝对远程URL,段直接从存储桶中获取,因此该门永远不会看到这些请求。 门控仍然阻止匿名访客_发现_段列表,但_泄漏或共享_段的URL仍然可以获取。 Stream protection + local distribution is airtight. Stream protection + Object Storage is good friction, not airtight.

它是如何工作的

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. 在这些请求中调用插件嵌入的引擎会使服务器崩溃,因此插件被排除在热路径之外:

成本发生了什么
Every non-exempt request验证cookie签名 + 过期valid passes. Missing or invalid gets a redirect to login, or a 401 for anything that is not a GET or HEAD
/页面仅加载可选引擎调用:onAuthCheck重新验证您的提供者,返回ok / refresh / deny

你的插件仅运行登录流程(不频繁,每个观众会话大约一次)和可选的每页加载onAuthCheck。 Owncast主机铸造并检查签名会话cookie,因此每个请求的检查仅限于签名和过期:没有数据库查找,没有插件调用。

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. 你的插件从不查看或设置令牌,因此它无法伪造或泄漏。 (这也是聊天自动获取观众身份的方式。 见下文聊天身份。)

构建一个门插件

门插件是一个HTTP服务插件,带有登录流程。 根据约定,控制循环根植于插件自己的命名空间/plugins/\<your-slug>/

三部分完成工作:

  1. 注册用户。 将外部身份转换为真实的Owncast用户,使用owncast.users.register。 传递一个稳定的、提供者范围的authId(例如"github:583231")。 主机通过你的slug给它命名空间,以便插件无法相互冲突或伪造。
  2. 授予会话。 使用该userId调用owncast.auth.grantSession。 Owncast主机铸造签名cookie并将其附加到传输中的响应。 这只在onHttpRequest处理程序内部工作。
  3. 重定向到首页。 当Owncast主机将未经身份验证的访客重定向到你的登录屏幕时,它会附加一个return_to查询参数,并将其清理为同源路径(以便不能被转变为开放重定向)。 在成功登录后将观众发送到那里。

要注销观众,请调用owncast.auth.endSession()并重定向。 你的插件仍然控制去哪里(它可能会重定向到提供者自己的注销)。

撤销与onAuthCheck

会话是无状态的,因此没有每个请求的"此用户是否仍然被允许"列表。 这将把插件放回热路径。 相反,定义可选的onAuthCheck处理程序。 它在每个/页面加载时触发,带有已解析的观众身份,并返回okrefresh(重新签发cookie,可选地带有新的TTL以滑动过期)或deny(结束会话并重定向到登录)。 一个依赖提供者的插件在这里重新检查会员资格(组织仍然有效? 账户未删除?)。

因为检查仅在/上运行,你撤销的观众保持任何打开的标签工作,直到他们重新加载或cookie过期。 会话TTL是强硬的后备以进行撤销,因此如果快速撤销很重要,请保持其短。

示例:共享密码门

basic-auth示例插件是最简单的门控:一个共享密码,一个共享的"访客"身份,没有外部提供者。 它在examples/js/basic-authexamples/python/basic-auth中提供。

其清单声明权限和一个用于密码的单个配置字段:

{
"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"
}
}
}

处理程序在/处呈现密码表单,检查提交的密码与配置的值,并在成功时注册共享身份、授予会话并重定向回来。 onAuthCheck读取可由管理员切换的revoked标志,以使每个人在下一个页面加载时都退出。 (下面省略了构建HTML表单的page()助手以简化。 见示例源。)

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();
},
});

对于真实的OAuth流程(CSRFstatestorage.kv,通过network.fetch进行代码交换,组织成员资格强制执行,以及根据owncast.server.info()构建的回调网址),请参见SDK中的github-auth示例。

启用该门

声明auth.gate本身并不会产生任何效果。 该门通过在管理员中正常启用/禁用生命周期启用插件来武装。 禁用它,门会立即放下。

  • 一次只能启用一个auth.gate插件。 Owncast拒绝在一个已存在的情况下启用第二个("先禁用另一个").
  • 启用之前进行配置。 插件可以在停用状态下安装和配置,然后启用以投入使用。 使用自动生成的配置表单输入凭据,如OAuth客户端ID和秘密。

失败安全

门的姿态与插件的健康状况脱钩。 如果门被启用但插件不可用(崩溃、加载失败、出错或经过重复故障后自动禁用),Owncast拒绝所有观众流量并提供静态的"身份验证暂时不可用"页面。 它不会意外打开。 管理员始终可达(管理员路线使用Owncast现有的基本身份验证并绕过该门),因此您可以修复配置或禁用插件。 已经有效的会话在停机期间仍能存活,因为检查cookie不需要插件调用。

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.

什么绕过门

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.

会话详情

  • Stateless signed cookie named owncast_session, HttpOnly, Secure (on HTTPS requests), SameSite=Lax, Path=/. 选择Lax而不是Strict,因为提供者回调是跨站点的顶层重定向。 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. 因为TTL是撤销的最后防线,所以它是真正的安全旋钮。
  • 签名秘密是Owncast主机的责任。 它在第一次使用时自动生成并保留在配置中。 轮换它会使每个会话无效(一个紧急按钮)。 插件作者从不接触它,并且它与任何OAuth_客户端_密钥分开,这也是插件要关心的配置。

聊天身份

门控登录会自动生成经过身份验证的聊天身份。 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. 没有令牌会被传输到浏览器的localStorage。 The viewer signs in once and shows up in chat under that name.

相关

  • 权限auth.gateusers.register
  • Owncast APIusers.registerauth.grantSessionauth.endSession
  • 事件onAuthCheck处理程序
  • 提供HTTP:登录流程构建的请求模型

Improve this page

See something missing or incorrect? Edit this page and improve the documentation for everyone.

Contributors to this documentation