oauth

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

OAuth (std/oauth)

OAuth (std/oauth)

Val 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.
This 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.
If 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.
Val Town 通过
std/oauth
提供零配置的“使用 Val Town 登录”功能。无需设置数据库,也无需配置提供商——只需包装 Hono fetch handler,即可开箱获得登录、注销和会话管理功能。会话存储在加密 Cookie 中,有效期为 30 天。
此功能仅用于 Val Town 账户登录。如需使用 Google、GitHub、Slack 等第三方 OAuth,请参阅
third-party-integrations
skill,其中按服务分别记录了相关流程。
如果目标是将应用限制为团队内部使用,而不是为应用创建自己的登录用户,那么限制 val 的应用访问权限是更简单的方案——平台会在代码运行前拦截端点,你无需编写任何身份验证代码。请参阅
restricted-access
skill。不要同时对同一个 val 应用这两种方案:受限 val 如果还运行
oauthMiddleware
,会导致访问者进行两次身份验证。

Imports

导入

ts
import {
  getOAuthUserData,
  oauthMiddleware,
} from "https://esm.town/v/std/oauth/middleware.ts";
ts
import {
  getOAuthUserData,
  oauthMiddleware,
} from "https://esm.town/v/std/oauth/middleware.ts";

Wrapping your app

包装应用

oauthMiddleware(handler)
takes your Hono fetch handler and returns a wrapped handler that injects three auto-managed routes:
  • GET /auth/login
    — starts the login flow
  • GET /auth/callback
    — completes the login flow
  • POST /auth/logout
    — clears the session
Export the wrapped handler as the val's default:
ts
import { Hono } from "npm:hono";
import { oauthMiddleware } from "https://esm.town/v/std/oauth/middleware.ts";

const app = new Hono();
app.onError((err) => Promise.reject(err));

app.get("/", (c) => c.text("hello"));

export default oauthMiddleware(app.fetch);
You don't write the
/auth/*
routes yourself — the middleware adds them. Don't shadow them in your own app.
oauthMiddleware(handler)
接收 Hono fetch handler,并返回一个包装后的 handler,该 handler 会注入三个自动管理的路由:
  • GET /auth/login
    ——启动登录流程
  • GET /auth/callback
    ——完成登录流程
  • POST /auth/logout
    ——清除会话
将包装后的 handler 导出为 val 的默认导出:
ts
import { Hono } from "npm:hono";
import { oauthMiddleware } from "https://esm.town/v/std/oauth/middleware.ts";

const app = new Hono();
app.onError((err) => Promise.reject(err));

app.get("/", (c) => c.text("hello"));

export default oauthMiddleware(app.fetch);
你无需自行编写
/auth/*
路由——中间件会自动添加这些路由。不要在自己的应用中覆盖它们。

Reading the current user

读取当前用户

Call
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.
ts
interface SessionData {
  user: {
    id: string;
    username: string | null;
    email: string | null;
    bio: string | null;
    tier: "free" | "pro" | null;
    type: "user" | "org";
    url: string;
    links: {
      self: string;
      profileImageUrl: string | null;
    };
  };
  accessToken: string; // Val Town API token (act on behalf of the user)
  refreshToken?: string;
  idToken?: string;
  expiresAt: number; // Unix timestamp (ms)
  isOrgMember?: boolean; // true if user belongs to this val's org
}
ts
app.get("/", async (c) => {
  const session = await getOAuthUserData(c.req.raw);
  if (session?.user) {
    return c.html(
      `<p>Logged in as ${session.user.username}</p>` +
      `<form method="POST" action="/auth/logout"><button>Log out</button></form>`
    );
  }
  return c.html(`<a href="/auth/login">Log in with Val Town</a>`);
});
可以从任意路由调用
getOAuthUserData(rawRequest)
。在 Hono 中,
rawRequest
是
c.req.raw
。如果请求已通过身份验证,它会返回会话数据,否则返回
null
。
ts
interface SessionData {
  user: {
    id: string;
    username: string | null;
    email: string | null;
    bio: string | null;
    tier: "free" | "pro" | null;
    type: "user" | "org";
    url: string;
    links: {
      self: string;
      profileImageUrl: string | null;
    };
  };
  accessToken: string; // Val Town API token (act on behalf of the user)
  refreshToken?: string;
  idToken?: string;
  expiresAt: number; // Unix timestamp (ms)
  isOrgMember?: boolean; // true if user belongs to this val's org
}
ts
app.get("/", async (c) => {
  const session = await getOAuthUserData(c.req.raw);
  if (session?.user) {
    return c.html(
      `<p>Logged in as ${session.user.username}</p>` +
      `<form method="POST" action="/auth/logout"><button>Log out</button></form>`
    );
  }
  return c.html(`<a href="/auth/login">Log in with Val Town</a>`);
});

Gating routes

为路由设置访问控制

There'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:
ts
app.get("/dashboard", async (c) => {
  const session = await getOAuthUserData(c.req.raw);
  if (!session?.user) return c.redirect("/auth/login");
  return c.html(`<h1>Welcome ${session.user.username}</h1>`);
});
没有内置的“要求登录”辅助函数——可以通过检查
getOAuthUserData
来保护路由;当会话不存在时,返回 401 或重定向到
/auth/login
:
ts
app.get("/dashboard", async (c) => {
  const session = await getOAuthUserData(c.req.raw);
  if (!session?.user) return c.redirect("/auth/login");
  return c.html(`<h1>Welcome ${session.user.username}</h1>`);
});

What you don't need to configure

无需配置的内容

  • No env vars — credentials and redirect URLs are handled by the platform.
  • No callback URL setup —
    /auth/callback
    is wired automatically.
  • No session store — sessions live in encrypted cookies.
  • 无需环境变量——凭据和重定向 URL 均由平台处理。
  • 无需设置回调 URL——
    /auth/callback
    已自动连接。
  • 无需会话存储——会话保存在加密 Cookie 中。

Verifying changes

验证变更

After 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.
添加 OAuth 后,对受保护的路由调用
fetch_val_endpoint
,确认未通过身份验证的请求会被重定向或返回 401。完整的登录流程需要真实的浏览器会话,无法仅通过
fetch_val_endpoint
测试——请分享在线 URL,并让用户尝试登录。