次のような場合に使用: Val(個人作成プログラム)のHTTPエンドポイント(インターネット経由でアクセスできる機能の入り口)をインターネット全体に公開したくないとき。具体的には、アプリを特定のチームだけに制限したい、エンドポイントがログインページにリダイレクトされる理由を知りたい、Webhook(外部からの自動通知)を許可したい、またはVal Townのユーザーがアプリを閲覧しているかを特定したいといった場合に活用できます。 このスキルでは、アプリアクセスの設定(`httpPrivacy`)、組織レベルでの権限付与、自動化用の認証トークン、さらに`X-Val-Town-User`という識別情報を含むヘッダー(通信データの付加情報)に対応しています。 Val内に独自のログイン機能を作成したい場合は、代わりに`oauth`スキルを参照してください。
Use when a val's HTTP endpoints should not be open to the whole internet — limiting an app to a team, understanding why an endpoint redirects to a login page, letting a webhook through, or identifying which Val Town user is viewing an app. Covers app access (`httpPrivacy`), org grants, bypass tokens for automation, and the `X-Val-Town-User` identity header. For building your own login flow inside a val, see the `oauth` skill instead.
Val(プログラムの単位)には、独立した2つのアクセス設定があります。一方を変更しても、もう一方には影響しません:
privacy: public / unlisted / private) — val.town上でソースコードを閲覧できる範囲httpPrivacy: public / restricted) — このvalのHTTPエンドポイント(通信機能)を呼び出せる範囲Valは、非公開のコードで公開されたエンドポイントを持つことも、公開コードで制限されたエンドポイントを持つこともできます。update_valのprivacyフィールドは最初の設定だけを変更し、アプリケーションアクセスはset_http_privacyで変更します。
アクセス制限機能は、この機能を有効にしている組織で利用できます。そのような組織内で作成されたvalは、デフォルトでrestrictedになる場合があります。新しいvalのURLが必ず公開されていると思い込まず、get_val_detail、list_vals、create_val、またはremix_valの応答から必ずhttpPrivacyを確認してください。
「アプリにログインを要求したい」という要望は、実は2つの異なる方法があります:
std/oauth(oauthスキルを参照)— valの内部で実行されます。ハンドラー(受信処理)をラップし、Val Townのアカウントを持つ誰もがログインできます。セッション(ログイン状態)を自分で管理し、ユーザーごとの機能を構築できます。内部ツールでチームだけがアクセスすべき場合は、アクセス制限機能を選択します。Val Townユーザーなら誰でもログインでき、アプリが独立したログイン管理を必要とする場合はstd/oauthを選択します。
両方を組み合わせないでください。既にアクセス制限されているvalにoauthMiddlewareを追加すると、訪問者は2回認証されることになります。制限されたvalが「誰が閲覧しているか」を知る必要がある場合は、Oauthを追加する代わりに、以下の身元ヘッダーを使用してください。
アクセス許可は個人ではなく、組織に付与されます。閲覧者は、そのvalが組織への許可を持っており、かつその閲覧者がその組織のメンバーである場合にアクセスできます。どちらか一方を削除すると、次のリクエストですぐアクセスが取り消されます。セッション中のキャッシュは行われません。
許可の付与元:
add_allowed_userで組織を許可、list_allowed_usersで現在の許可を表示、remove_allowed_userで取り消しlist_allowed_usersに表示されます。認証されていないリクエストはvalに到達しません。プラットフォームは302リダイレクト(別ページへの転送)で、Val Townのログインまたは認可ページに誘導します。これが制限されたvalをデバッグするときの最も一般的な混乱の原因です:
fetch_val_endpointがフォローしないリダイレクトを報告するcurlがレスポンスではなくval.townへの302を表示する403エラーを受け取るこれらはvalのコードが壊れていることを意味しません。まずhttpPrivacyを確認してください。restrictedなら、ゲート(入口)が正常に機能しています。set_http_privacyでvalを公開にするか、呼び出し元の組織にアクセスを許可するか、バイパストークンを使用してください。
機械はログインリダイレクトを完了できないため、webhookを受け取る制限されたval(Stripe、GitHub、別のvalのcronジョブなど)にはバイパストークン(そのval専用のシークレット)が必要です。
create_bypass_tokenで作成します。シークレットは1回だけ表示され、その後は取得できません。list_bypass_tokensとrevoke_bypass_tokenでトークンを管理します。
以下のどちらかの方法で提示します:
// ヘッダー(推奨 — シークレットをログと参照元から隠す)
await fetch(url, { headers: { "X-Val-Town-Access": Deno.env.get("MY_BYPASS_TOKEN")! } });
// クエリパラメータ(URLだけを受け入れるサービス向け。例:一部のwebhook設定)
await fetch(`${url}?val_town_access=${Deno.env.get("MY_BYPASS_TOKEN")}`);
プラットフォームはハンドラーが実行される前にヘッダーとクエリパラメータを削除するため、コードには見えません。バイパストークンリクエストは訪問者身元を持たず、匿名の機械呼び出しです。
ゲートを通ってきた人間の訪問者に対し、プラットフォームは短期間有効な署名済みX-Val-Town-Userヘッダーを転送します。これは身元そのものではなく、valの独自APIトークン(Val Townがvaltown環境変数として挿入)を使って閲覧者のプロフィールと交換する必要があります:
const IDENTITY_HEADER = "X-Val-Town-User";
/** 閲覧者の公開プロフィールを返す、または存在しない場合はnullを返す */
async function getViewer(req: Request) {
const signed = req.headers.get(IDENTITY_HEADER);
if (!signed) return null;
const res = await fetch("https://api.val.town/v3/val/viewer", {
headers: {
Authorization: `Bearer ${Deno.env.get("valtown")}`,
[IDENTITY_HEADER]: signed,
},
});
if (!res.ok) return null;
// { id, username, type, bio, profileImageUrl, url, links }
return await res.json();
}
重要なルール:
!でアサーション(断定)したり、nullの結果にアクセスしたりしないでください。上記の通信方法(X-Val-Town-Userとそれに続く/v3/val/viewer交換)は現在の実装ですが変わる可能性があります。ただし3つのルールは変わりません。
| 操作 | 使用ツール |
|---|---|
| 現在の設定を確認 | get_val_detail(httpPrivacyフィールド) |
| エンドポイントを公開または制限に | set_http_privacy |
| アクセス権を持つユーザーを表示 | list_allowed_users |
| 組織を許可/取り消し | add_allowed_user / remove_allowed_user |
| 自動化シークレットを作成/一覧/取り消し | create_bypass_token / list_bypass_tokens / revoke_bypass_token |
制限されたvalはval.townでのみiframe埋め込み(別サイトへの埋め込み表示)が可能なため、外部サイトへの埋め込みはログイン状況に関わらずブラウザによってブロックされます。
A val has two independent access settings. Changing one does not change the other:
privacy: public / unlisted / private) — who can read the source on val.town.httpPrivacy: public / restricted) — who can call the val's HTTP endpoints.A val can have private code and a wide-open endpoint, or public code and a locked-down endpoint. update_val's privacy field only moves the first one; app access is changed with set_http_privacy.
Restricted app access is available to organizations that have the feature enabled. Vals created in such an org may default to restricted — always read httpPrivacy off a get_val_detail, list_vals, create_val, or remix_val response rather than assuming a new val's URL is open.
Two different things both sound like "make my app require a login":
std/oauth (see the oauth skill) runs inside your val: you wrap your handler, and anyone with a Val Town account can log in. You control the session and can build per-user features.Pick restricted access for an internal tool that only your team should reach. Pick std/oauth when any Val Town user may sign in and the app needs its own notion of a logged-in user.
Don't stack them by accident. Adding oauthMiddleware to an already-restricted val means the visitor authenticates twice — once at the gate, once in your code. If a restricted val needs to know who is viewing, use the identity header below instead of adding OAuth.
Access is granted to organizations, not individual people. A viewer gets in when the val has a grant to an org and that viewer is a member of it. Removing either one revokes access on the very next request — nothing is cached for the length of a session.
Grants come from:
add_allowed_user grants an org, list_allowed_users shows current grants, remove_allowed_user revokes one.list_allowed_users alongside direct grants.An unauthenticated request does not reach the val. The platform answers with a 302 redirect to a Val Town login or authorization page. This is the single most common source of confusion when debugging a restricted val:
fetch_val_endpoint reports a redirect it won't follow.curl shows a 302 to val.town instead of your response.403 explaining they need access to their organization.None of these mean the val's code is broken. Check httpPrivacy first — if it's restricted, the gate is doing its job. Make the val public with set_http_privacy, grant the caller's org, or use a bypass token.
Machines can't complete a login redirect, so a restricted val that receives webhooks (Stripe, GitHub, a cron job in another val) needs a bypass token — a secret scoped to that one val.
Create it with create_bypass_token; the secret is shown once and cannot be retrieved again. Manage tokens with list_bypass_tokens and revoke_bypass_token.
Present it either way:
// Header (preferred — keeps the secret out of logs and referrers)
await fetch(url, { headers: { "X-Val-Town-Access": Deno.env.get("MY_BYPASS_TOKEN")! } });
// Query param (for services that only accept a URL, e.g. some webhook configs)
await fetch(`${url}?val_town_access=${Deno.env.get("MY_BYPASS_TOKEN")}`);
The platform strips the header and the query param before your handler runs, so your code never sees them. A bypass-token request carries no viewer identity — it is an anonymous machine caller.
For a human viewer who came in through the gate, the platform forwards a short-lived signed X-Val-Town-User header. It is not the identity itself — exchange it for the viewer's profile using the val's own API token, which Val Town injects as the valtown environment variable:
const IDENTITY_HEADER = "X-Val-Town-User";
/** Returns the viewer's public profile, or null when there isn't one. */
async function getViewer(req: Request) {
const signed = req.headers.get(IDENTITY_HEADER);
if (!signed) return null;
const res = await fetch("https://api.val.town/v3/val/viewer", {
headers: {
Authorization: `Bearer ${Deno.env.get("valtown")}`,
[IDENTITY_HEADER]: signed,
},
});
if (!res.ok) return null;
// { id, username, type, bio, profileImageUrl, url, links }
return await res.json();
}
Rules that matter:
!-assert it or index into a null result.The transport above (X-Val-Town-User plus the /v3/val/viewer exchange) is how this works today and may change; the three rules hold regardless.
| Task | Tool |
|---|---|
| Check the current setting | get_val_detail (httpPrivacy field) |
| Make an endpoint public or restricted | set_http_privacy |
| See who has access | list_allowed_users |
| Grant / revoke an org | add_allowed_user / remove_allowed_user |
| Create / list / revoke automation secrets | create_bypass_token / list_bypass_tokens / revoke_bypass_token |
Restricted vals can only be iframed by val.town, so an embed of one on an external site will be blocked by the browser regardless of who's logged in.
原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。