• Projects
  • Service
  • About
  • branding.bz
  • Podcast
  • Tips
  • FAQ
  • Recruit
  • Download
  • Contact
  • branding.bz(ブランド構築SaaS)
  • DESIGN NOW(デザインメディア)
  • X
  • LinkedIn
  • Spotify
  • Facebook

213-0011 神奈川県川崎市高津区久本3-6-7-303

© 2026 ID INC. All rights reserved

claude-skills/スキル
SKILLKnowledge Workdeployment

restricted-access

プラグイン
Val Town
ソース
GitHub で見る ↗
説明

次のような場合に使用: 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.

ユースケース
  • HTTPエンドポイントを特定チームのみに限定したい
  • リダイレクト理由を理解したい
  • webhookを許可したい
  • Val Townユーザーを特定したい
本文(日本語訳)

制限付きアプリアクセス

Val(プログラムの実行単位)には、独立した2つのアクセス設定があります。一方を変更してももう一方は変更されません:

  • コード(privacy: public / unlisted / private)— val.town上でソースコードを読むことができる人
  • アプリアクセス(httpPrivacy: public / restricted)— Valが提供するHTTP通信の接続口を使用できる人

Valは、非公開コードで広く開かれた接続口を持つことも、公開コードで厳密に制限された接続口を持つこともできます。update_valのprivacyフィールドは最初の設定のみを変更し、アプリアクセスはset_http_privacyで変更します。

制限付きアプリアクセスは、この機能を有効にしている組織で利用可能です。そのような組織内で作成されたValは、デフォルトでrestrictedになる場合があります。新しく作成されたValの接続口が必ず開かれていると仮定せず、常にget_val_detail、list_vals、create_val、またはremix_valの応答からhttpPrivacyを読み取ってください。

これが適切なツールか?

「アプリにログインを必須にする」という目的に対して、異なる2つの方法があります:

  • 制限付きアプリアクセス(このスキル)— プラットフォームの入り口でアクセスを制限し、あなたのコードが実行される前に遮断します。アクセス権がない人からのリクエストはValに到達しません。認証コードを書く必要がありません。アクセス権は個人ではなく組織全体に与えられます。
  • std/oauth(oauthスキルを参照)— Valの内部で実行されます。ハンドラー(処理の仲介役)をラップして、Val Townアカウントを持つ誰もがログインできるようにします。セッション管理はあなたが制御でき、ユーザー単位の機能を構築できます。

内部ツールでチームだけがアクセスすべき場合は制限付きアクセスを、Val Townのどのユーザーもログインでき、アプリが独自のログイン状態を管理する必要がある場合はstd/oauthを選択してください。

誤ってこの2つを組み合わせないでください。既に制限されているValにoauthMiddlewareを追加すると、訪問者が2回認証されることになります。制限されたValが誰が閲覧しているかを知る必要がある場合は、Oauthを追加する代わりに、以下の識別ヘッダーを使用してください。

アクセス許可を得られる者

アクセス権は個人ではなく組織に付与されます。閲覧者がアクセスできるのは、Valがある組織へのアクセス許可を持っているかつその閲覧者がその組織のメンバーである場合です。どちらか一方が削除されると、次のリクエストで即座にアクセスが失効します。セッション期間中のキャッシュはありません。

アクセス許可は以下から付与されます:

  • 直接付与 — add_allowed_userで組織にアクセス許可を与え、list_allowed_usersで現在の許可を表示し、remove_allowed_userで取り消します。
  • ドメイン規則 — 特定のドメインのメールアドレスを持つ全員をValが許可できます。これらは直接付与と並んでlist_allowed_usersに表示されます。
  • 招待状 — 許可された組織外の者が招待メールを受け取り、受け入れるとアクセス権を得られます。
  • バイパストークン — マシン用のプロジェクトスコープ(適用範囲が限定された)な秘密鍵。以下を参照してください。

その他の人に見えるもの

認証されていないリクエストは、Valに到達しません。プラットフォームは302リダイレクトでVal Townのログインまたは認可ページに誘導します。これが制限されたValをデバッグするときに最も一般的な混乱の原因です:

  • fetch_val_endpointが従わないリダイレクトを報告する
  • curlがレスポンス代わりにval.townへの302を表示する
  • APIクライアントが期待していたJSONの代わりにログインページのHTMLを受け取る
  • アクセス権がなく、ただしログイン済みの訪問者が、組織へのアクセスが必要であることを説明する403エラーを受け取る

これらのどれもValのコードが壊れていることを意味しません。まずhttpPrivacyを確認してください。restrictedであれば、ゲートが正常に機能しています。set_http_privacyでValを公開にするか、呼び出し元の組織にアクセス許可を与えるか、バイパストークンを使用してください。

自動化とウェブフック

マシンはログインリダイレクトを完了できないため、ウェブフック(Stripe、GitHub、別のValの定時実行タスク)を受け取る制限されたValにはバイパストークン(その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だけを受け取るサービス用。例:一部のウェブフック設定)
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();
}

重要なルール:

  • 常に閲覧者をオプション扱いしてください。 公開Valまたはバイパストークンリクエストには存在しません。!で断定したり、null結果にインデックスでアクセスしないでください。
  • サーバー側で、各リクエストごとに解決してください。 この認証情報は短期有効でValに紐付けられているため、永続化したり、ブラウザに渡したり、クライアント側コードから検索を試みないでください。
  • 公開プロフィールフィールドのみが返されます — ハンドル名、自己紹介、アバター、プロフィールURL。メールアドレスや料金プランはありません。それらに依存するValを作成しないでください。

上記の方式(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埋め込み可能(別のウェブサイトにページ内に埋め込むことができる状態)のため、外部サイトへの埋め込みはログイン状況に関わらずブラウザによってブロックされます。

原文(English)を表示

Restricted App Access

A val has two independent access settings. Changing one does not change the other:

  • Code (privacy: public / unlisted / private) — who can read the source on val.town.
  • App access (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.

Is this the right tool?

Two different things both sound like "make my app require a login":

  • Restricted app access (this skill) gates the endpoint at the platform edge, before your code runs. Requests from people without access never reach the val. You write no auth code. Access is granted to whole organizations, not to individuals.
  • 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.

Who gets through

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:

  • Direct grants — add_allowed_user grants an org, list_allowed_users shows current grants, remove_allowed_user revokes one.
  • Domain rules — a val can admit everyone with an email address at a given domain. These appear in list_allowed_users alongside direct grants.
  • Invitations — someone outside a granted org can be invited by email and gains access when they accept.
  • Bypass tokens — a project-scoped secret for machines; see below.

What everyone else sees

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.
  • An API client gets HTML from a login page where it expected JSON.
  • A visitor without access who is logged in gets a 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.

Automation and webhooks

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.

Identifying the viewer

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:

  • Always treat the viewer as optional. There is none on a public val or a bypass-token request. Never !-assert it or index into a null result.
  • Resolve it server-side, on each request. The credential is short-lived and tied to your val — don't persist it, hand it to the browser, or attempt the lookup from client-side code.
  • Only public profile fields come back — handle, bio, avatar, profile URL. No email address, no billing tier. Don't build a val that depends on those.

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.

Managing app access

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 による自動翻訳です。