認証
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.
このページのすべては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 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. オブジェクトストレージまたは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 | クッキーの署名 + 有効期限を検証します | 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を返します |
あなたのプラグインはログインフロー(頻繁ではなく、ユーザーセッションごとに約1回)とオプションの各ページロードのonAuthCheckのみを実行します。 Owncastホストは署名されたセッションクッキーを発行し、チェックします。したがって、リクエストごとのチェックは署名のみと有効期限の確認になります:データベースルックアップなし、プラグイン呼び出しなし。
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 servingプラグインです。 制御ループは慣習的に、あなたのプラグイン自身の名前空間/plugins/\<your-slug>/に基づいています:
3つの部分が作業を行います:
- ユーザーを登録します。 外部のアイデンティティを実際のOwncastユーザーに変換します:
owncast.users.register。 安定した、プロバイダスコープのauthIdを渡します(例:"github:583231")。 ホストはスラッグで名前空間を設定し、プラグインが衝突したり互いに偽装したりできないようにします。 - セッションを付与します。
owncast.auth.grantSessionをそのuserIdで呼び出します。 Owncastホストは署名されたクッキーを発行し、フライト中の応答に添付します。 これはonHttpRequestハンドラー内のみで機能します。 - ホームにリダイレクトします。 Owncastホストは、未認証の訪問者をログイン画面へバウンスするとき、
return_toクエリパラメーターを追加し、それを同一オリジンのパスに消毒します(オープンリダイレクトに変えてはいけません)。 成功したログインの後、観客をそこに送ります。
観客をサインアウトするには、owncast.auth.endSession()を呼び出し、リダイレクトします。 あなたのプラグインはどこに(プロバイダ自身のログアウトにバウンスする可能性があります)を制御します。
onAuthCheckによる取り消し
セッションはステートレスであり、そのためリクエストごとの「このユーザーは依然として許可されるか」リストは存在しません。 それはプラグインをホットパスに戻すことになります。 代わりに、オプションのonAuthCheckハンドラーを定義します。 それは各/ページの読み込み時に発生し、解決されたビューワーアイデンティティを返し、ok、refresh(クッキーを再発行し、オプションでスライディング有効期限のための新しいTTLで)、またはdenyを返します(セッションを終了し、ログインにバウンス)。 プロバイダバックのプラグインはここでメンバーシップを再確認します(組織は依然として有効ですか? アカウントは削除されていませんか?)。
チェックは/でのみ実行されるため、取り消されたビューワーは、リロードするか、クッキーが期限切れになるまで、すべての開いているタブを機能させます。 セッションのTTLは厳格なバックストップですであり、迅速な取り消しが重要である場合は短く保つ必要があります。
動作例:共有パスワードゲート
basic-authの例プラグインは最もシンプルなゲートです:1つの共有パスワード、1つの共有「ゲスト」アイデンティティ、外部プロバイダなし。 これはexamples/js/basic-authとexamples/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()ヘルパーは簡潔さのために省略されています。 例のソースを参照してください。)
- 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()
実際のOAuthフロー(CSRF stateはstorage.kvに配置され、コード交換がnetwork.fetchを介して行われ、組織メンバーシップの強制が行われ、コールバックURLがowncast.server.info()から作成されます)に関しては、SDKのgithub-authの例を参照してください。
ゲートの有効化
auth.gateを宣言しても、自体では何も起こりません。 ゲートはプラグインを有効にすることによって武装されます。管理者内での通常の有効化/無効化ライフサイクルを通じて。 無効化すると、ゲートは瞬時に落ちます。
- 同時に有効にできる
auth.gateプラグインは1つだけです。 Owncastはすでにライブ中の他のプラグインを有効にすることを拒否します(「他を先に無効にしてください」)。 - 有効にする前に構成してください。 プラグインは無効のままでインストールおよび構成できます。その後、有効にしてライブにできます。 OAuthクライアントIDやシークレットなどの資格情報は、自動生成された設定フォームを使用します。
閉じる場合
ゲートの姿勢はプラグインの健康状態から切り離されています。 ゲートが武装解除しても、プラグインが利用できない場合(クラッシュ、読み込みに失敗、エラー、または繰り返しの失敗後に自動的に無効化された場合)、Owncastはすべてのビューワートラフィックを拒否し、静的な「認証は一時的に使用不可」ページを表示します。 決してオープンにはなりません。 管理者は常にアクセス可能です(管理者ルートはOwncastの既存の基本認証を使用し、ゲートをバイパスします)ので、設定を修正したり、プラグインを無効にしたりできます。 すでに有効なセッションは、クッキーの確認がプラグイン呼び出しを必要としないため、障害を生き延びます。
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 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.
セッションの詳細
- Stateless signed cookie named
owncast_session,HttpOnly,Secure(on HTTPS requests),SameSite=Lax,Path=/. プロバイダーコールバックがクロスサイトのトップレベルリダイレクトであるため、StrictではなくLaxです。 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. 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.gate、users.register - Owncast APIs:
users.register、auth.grantSession、auth.endSession - イベント:
onAuthCheckハンドラー - HTTPを提供する:ログインフローが構築されているリクエストモデル
Improve this page
See something missing or incorrect? Edit this page and improve the documentation for everyone.
