{"id":7644,"plugin_id":"plugin_asdk_app_6a5e7ac6ddf881919de226cb7506ef57","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T22:50:58.148Z","digest":"f3953c61ebdabd6c7a70d6ee7ed4db90847f3214921847affcec0fc8458bb1b1","against":null,"payload":{"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"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}