A middleware library for adding OAuth authentication to Val Town vals using the Val Town OAuth provider.
- Public Client Flow: Uses OAuth 2.0 Authorization Code flow with PKCE for secure public client authentication
- Dynamic Client Registration: Automatically registers OAuth clients on-demand
- State Encryption: Encrypts OAuth state parameter with AES-GCM to prevent tampering
- Encrypted Session Cookies: Stateless session management using encrypted cookies
- CSRF Protection: Origin header validation for non-GET requests
- Hono Middleware: Drop-in middleware for Hono apps
/** @jsxImportSource https://esm.sh/hono/jsx */
import { Hono } from "https://esm.sh/hono";
import oauth from "https://esm.town/v/std/oauth/middleware";
const app = new Hono();
app.get("/", (c) => {
const userId = c.req.header("X-VT-User-Id");
return c.html(
<html>
<body>
{userId
? <p>Logged in as {userId}</p>
: <a href="/auth/login">Log in</a>}
</body>
</html>,
);
});
// Wrap your app with the OAuth middleware
export default oauth(app.fetch);
// Or customize the options:
// export default oauth(app.fetch, {
// clientName: "My Custom App Name",
// scopes: ["openid", "offline_access", "user_r", "project_r"]
// });
The middleware automatically handles these routes:
/auth/login- Initiates the OAuth flow, redirects to Val Town authorization/auth/callback- Handles the OAuth callback, exchanges code for tokens, and creates session/auth/logout- Logs out the user and clears the session cookie
Once authenticated, all requests include these trusted headers:
X-VT-User-Id- The Val Town user IDX-VT-User-Email- The user's email addressX-VT-User-Name- The user's display name
These headers are set by the middleware after validating the session cookie.
Any user-provided X-VT-* headers are removed to ensure they're trusted.
Optional:
OAUTH_STATE_ENCRYPTION_KEY- Secret key for encrypting OAuth state and sessions- If not set, a random key will be auto-generated and stored in blob storage
- Performance tip: Set this to a random value (e.g.,
openssl rand -base64 32) to dramatically speed up your val by avoiding blob storage lookups on every request - The key is cached in memory after first use for optimal performance
Core utilities for OAuth operations:
- PKCE (Proof Key for Code Exchange) generation
- State encryption/decryption using AES-GCM
- Dynamic client registration with Val Town OAuth server
- State validation with 10-minute expiry
Session management utilities:
- Encrypted session cookie creation and validation
- 30-day session duration
- Stateless design - no database required
- Stores OAuth tokens (access_token, refresh_token, id_token) and user info
Hono middleware that:
- Intercepts auth-related routes (
/auth/*) - Manages the OAuth authorization flow
- Creates and validates encrypted session cookies
- Sets trusted user headers
- Provides CSRF protection for non-GET requests
Demo application showing how to use the middleware
Sessions are stored as encrypted cookies with the following characteristics:
- Storage: Encrypted cookie (stateless, no database)
- Duration: 30 days (fixed, no rotation)
- Cookie Settings: HttpOnly, Secure, SameSite=Lax
- Contents: User ID, email, name, and OAuth tokens (access, refresh, ID)
- Encryption: AES-GCM using the same key as OAuth state encryption
While user headers provide basic identity information, the full OAuth tokens (including access tokens for API calls) are stored securely in the encrypted session cookie. To access tokens in your application:
// The middleware sets these headers for you
const userId = c.req.header("X-VT-User-Id");
const userEmail = c.req.header("X-VT-User-Email");
const userName = c.req.header("X-VT-User-Name");
// For accessing OAuth tokens, you'll need to implement a helper
// that reads and decrypts the session cookie (similar to how the
// middleware does it internally)
- PKCE: Protects against authorization code interception attacks
- Encrypted State: Prevents CSRF and state tampering
- Short-lived State: 10-minute expiry on state parameters
- Public Client: No client secrets stored - uses PKCE instead
- Encrypted Sessions: Cookie contents are encrypted and tamper-proof
- Trusted Headers: User-provided
X-VT-*headers are removed - CSRF Protection: Origin header validation for POST/PUT/DELETE requests
- Secure Cookies: HttpOnly, Secure, SameSite=Lax flags
- userInfo endpoint: Currently may return null or incomplete data
- This is a known issue with the Val Town OAuth server
- User ID may show as "unknown" until this is resolved
- No session rotation: Sessions are valid for 30 days without rotation
- No server-side revocation: Sessions can't be revoked server-side (stateless design)
- Debug why userInfo returns
nullor incomplete data - Consider moving cache information from blob storage to sqlite for performance
- Add optional session rotation support
- Add helper function to extract full session data (including tokens) from cookies