← Val TownCONTENT HISTORY

Update to Val Town

Snapshot Sep 30, 2026 · 22:50 UTC · version 3.0.0

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "name": "oauth",
  "description": "Use when a val needs to require login with a Val Town account — gating routes behind authentication, identifying the current user, building user-specific dashboards. Covers std/oauth's `oauthMiddleware` and `getOAuthUserData`, the auto-managed `/auth/*` routes, and session behavior. For third-party OAuth providers (Google, GitHub, etc.) see the `third-party-integrations` skill instead.",
  "included_files": [],
  "skill_md_contents": "---\nname: oauth\ndescription: Use when a val needs to require login with a Val Town account — gating routes behind authentication, identifying the current user, building user-specific dashboards. Covers std/oauth's `oauthMiddleware` and `getOAuthUserData`, the auto-managed `/auth/*` routes, and session behavior. For third-party OAuth providers (Google, GitHub, etc.) see the `third-party-integrations` skill instead.\n---\n\n# OAuth (std/oauth)\n\nVal Town provides zero-config \"Log in with Val Town\" via `std/oauth`. No database setup, no provider config — wrap your Hono fetch handler and you get login, logout, and session management for free. Sessions are stored in encrypted cookies and last 30 days.\n\nThis is for **Val Town account login only**. For Google / GitHub / Slack / etc. OAuth, see the `third-party-integrations` skill — those flows are documented per-service.\n\nIf the goal is to keep an app internal to a team rather than to give it its own logged-in users, restricting the val's app access is the simpler answer — the platform gates the endpoint before your code runs, and you write no auth code. See the `restricted-access` skill. Don't apply both to one val: a restricted val that also runs `oauthMiddleware` makes visitors authenticate twice.\n\n## Imports\n\n```ts\nimport {\n  getOAuthUserData,\n  oauthMiddleware,\n} from \"https://esm.town/v/std/oauth/middleware.ts\";\n```\n\n## Wrapping your app\n\n`oauthMiddleware(handler)` takes your Hono fetch handler and returns a wrapped handler that injects three auto-managed routes:\n\n- `GET /auth/login` — starts the login flow\n- `GET /auth/callback` — completes the login flow\n- `POST /auth/logout` — clears the session\n\nExport the wrapped handler as the val's default:\n\n```ts\nimport { Hono } from \"npm:hono\";\nimport { oauthMiddleware } from \"https://esm.town/v/std/oauth/middleware.ts\";\n\nconst app = new Hono();\napp.onError((err) => Promise.reject(err));\n\napp.get(\"/\", (c) => c.text(\"hello\"));\n\nexport default oauthMiddleware(app.fetch);\n```\n\nYou don't write the `/auth/*` routes yourself — the middleware adds them. Don't shadow them in your own app.\n\n## Reading the current user\n\nCall `getOAuthUserData(rawRequest)` from any route. In Hono, `rawRequest` is `c.req.raw`. It returns the session data if the request is authenticated, or `null` otherwise.\n\n```ts\ninterface SessionData {\n  user: {\n    id: string;\n    username: string | null;\n    email: string | null;\n    bio: string | null;\n    tier: \"free\" | \"pro\" | null;\n    type: \"user\" | \"org\";\n    url: string;\n    links: {\n      self: string;\n      profileImageUrl: string | null;\n    };\n  };\n  accessToken: string; // Val Town API token (act on behalf of the user)\n  refreshToken?: string;\n  idToken?: string;\n  expiresAt: number; // Unix timestamp (ms)\n  isOrgMember?: boolean; // true if user belongs to this val's org\n}\n```\n\n```ts\napp.get(\"/\", async (c) => {\n  const session = await getOAuthUserData(c.req.raw);\n  if (session?.user) {\n    return c.html(\n      `<p>Logged in as ${session.user.username}</p>` +\n      `<form method=\"POST\" action=\"/auth/logout\"><button>Log out</button></form>`\n    );\n  }\n  return c.html(`<a href=\"/auth/login\">Log in with Val Town</a>`);\n});\n```\n\n## Gating routes\n\nThere's no built-in \"require login\" helper — gate routes by checking `getOAuthUserData` and returning a 401 or redirecting to `/auth/login` when the session is missing:\n\n```ts\napp.get(\"/dashboard\", async (c) => {\n  const session = await getOAuthUserData(c.req.raw);\n  if (!session?.user) return c.redirect(\"/auth/login\");\n  return c.html(`<h1>Welcome ${session.user.username}</h1>`);\n});\n```\n\n## What you don't need to configure\n\n- No env vars — credentials and redirect URLs are handled by the platform.\n- No callback URL setup — `/auth/callback` is wired automatically.\n- No session store — sessions live in encrypted cookies.\n\n## Verifying changes\n\nAfter adding OAuth, call `fetch_val_endpoint` on a gated route to confirm it redirects or 401s when unauthenticated. The full login flow requires a real browser session and can't be exercised by `fetch_val_endpoint` alone — share the live URL and have the user try logging in.\n"
}

SHA-256: f3953c61ebdabd6c7a70d6ee7ed4db90847f3214921847affcec0fc8458bb1b1