This guide explains how to test the @std/oauth library end-to-end using Playwright and Val Town's MCP server.
- Access to Val Town MCP server tools (
mcp__val-town__*andmcp__playwright__*) - Test email address:
stevekrouse-6ca82204b4fa11f0ad790224a6c84d84@valtown.email - The
@stevekrouse/login-to-val-town-shimval must be set up to capture login emails
If you're not already logged in to Val Town in the browser:
-
Navigate to the sign-in page:
mcp__playwright__browser_navigate({ url: "https://www.val.town/auth/signin/", }); -
Enter the test email address in the email field and click "Continue"
-
Retrieve the login link from SQLite:
mcp__val - town__sqlite_execute({ sql: "SELECT text, timestamp FROM login_to_val_town_shim_emails ORDER BY timestamp DESC LIMIT 1", }); -
Extract the login URL from the email text (look for the
https://clerk.val.town/v1/verify?...URL) -
Navigate to the login URL in Playwright to complete sign-in
Once logged in to Val Town, test the OAuth flow:
-
Navigate to the OAuth example app:
mcp__playwright__browser_navigate({ url: "https://std--91178458b42e11f0b6f30224a6c84d84.web.val.run", });Expected result: Page shows "Val Town Auth Demo" with a "Log in" link
-
Click the "Log in" link:
mcp__playwright__browser_click({ element: "Log in link", ref: "e3" });Expected result: Redirects to Val Town OAuth authorization page showing:
- "Authorize Application" heading
- "Generic Val Town App would like to access your Val Town account"
- List of requested permissions (basic profile, read vals, user_r scope)
- "Deny" and "Allow" buttons
-
Click "Allow":
mcp__playwright__browser_click({ element: "Allow button", ref: "e19" });Expected result: Redirects back to the callback URL with JSON response containing:
{ "success": true, "tokens": { "access_token": "...", "expires_in": 86400, "id_token": "...", "refresh_token": "...", "scope": "openid offline_access", "token_type": "Bearer" }, "userInfo": null, "protectedData": { "accountId": "...", "clientId": "...", ... } }
- Dynamic client registration
- PKCE flow (code challenge/verifier)
- State encryption with 10-minute expiry
- Authorization code exchange for tokens
- Access token, ID token, and refresh token retrieval
- No session management: The callback returns JSON tokens instead of creating a session
- No user headers: X-VT-User-Id, X-VT-User-Email, X-VT-User-Name are not set
- userInfo returns null: The userinfo endpoint call may be failing
- No /auth/logout: Logout endpoint is not implemented
SELECT text, timestamp, datetime(timestamp/1000, 'unixepoch') as datetime
FROM login_to_val_town_shim_emails
ORDER BY timestamp DESC
LIMIT 5
DELETE FROM login_to_val_town_shim_emails
WHERE timestamp < (SELECT MAX(timestamp) - 3600000 FROM login_to_val_town_shim_emails)
- The OAuth flow works but doesn't maintain sessions between requests
- The example app currently just displays the raw JSON response
- To test with a fresh authorization, revoke access in Val Town account settings first
- The login-to-val-town-shim stores emails in SQLite for secure access (not publicly accessible via HTTP)