FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
lightweight
lightweightglimpse2-runbook-view-glimpse-save-login-react
Remix of lightweight/glimpse2-runbook-view-glimpse-save-login
Public
Like
glimpse2-runbook-view-glimpse-save-login-react
Home
Code
8
_townie
13
backend
7
frontend
5
shared
3
.vtignore
README.md
deno.json
H
main.tsx
Branches
1
Pull requests
Remixes
History
Environment variables
6
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
_townie
/
02-auth.md
Code
/
_townie
/
02-auth.md
Search
9/5/2025
Viewing readonly version of main branch: v10
View latest version
02-auth.md

Complete Authentication Implementation Instructions

Townie instructions (Townie should follow these)

Read through all of these instructions in order to understand the architecture of this Val, and implementation details important to this Val, and then once you have it all in your memory you should get to work.

If anything is unclear, you should stop and ask me for clarification before proceeding. Do not write code if you are unsure of what is expected.

Important: there are checkpoints in these instructions where you are directed to stop and ask permission before proceeding. Follow those instructions and do not proceed without permission.

LastLogin Built-in Features

Important: LastLogin automatically handles certain routes and authentication flows:

  • /auth/logout - LastLogin intercepts this route and handles logout automatically
  • No custom logout route needed in your Hono app
  • Simply link to /auth/logout and LastLogin will clear the session and redirect
  • Authentication headers - LastLogin adds X-LastLogin-Email header for authenticated requests

Authentication Flow:

  1. Unauthenticated requests → LastLogin checks for session
  2. No session → Returns 401 or redirects to login
  3. Valid session → Adds X-LastLogin-Email header and passes to your app
  4. /auth/logout requests → LastLogin clears session automatically (never reaches your app)

Step 1. Core Architecture Setup

Main Application (main.tsx)

Add global authentication middleware to main.tsx:

import { lastlogin } from "https://esm.town/v/stevekrouse/lastlogin_safe"; // Import route modules import authCheck from "./backend/routes/authCheck.ts"; // other route modules const app = new Hono(); // Apply global authentication middleware to ALL routes app.use("*", authCheck); // other routes // Export with LastLogin wrapper async function handler(request: Request): Promise<Response> { return app.fetch(request); } // Wrap and export the main HTTP handler with lastlogin export default lastlogin(handler);

Dashboard Route Organization

Best Practice: Separate the root dashboard route from main.tsx

Instead of defining the root route directly in main.tsx, create a dedicated dashboard route file:

  • Create: /backend/routes/views/dashboard.tsx
  • Purpose: Handle the authenticated user's main dashboard/landing page
  • import dashboardRoute from "./backend/routes/views/dashboard.tsx";
  • Mount in main.tsx: app.get("/", dashboardRoute)
  • Note: Dashboard is placed in /routes/views/ because it's a user-facing view, not an API endpoint

This approach:

  • Keeps main.tsx focused on app setup and middleware
  • Makes the dashboard logic easier to maintain and test
  • Follows the modular architecture pattern with views separated from API routes
  • Places user-facing interfaces in /routes/views/ for better organization
  • Allows for easier dashboard enhancements without touching core routing

The dashboard route should:

  • Display user information (email from c.get("userEmail"))
  • Display the /api/health endpoint results as raw JSON in a
     tag; e.g., JSON.stringify(data, null, 2)
  • Include logout functionality via /auth/logout
  • Show a simple and professional welcome interface with minimal styling

Create /backend/routes/authCheck.ts

Authentication Middleware:

  • Retrieve the user's email: const email = c.req.header("X-LastLogin-Email")
  • If email exists, the user is authenticated - store in context and continue to your app
  • If no email, show login page with LoginWithGoogleButton
  • Note: You don't need to handle /auth/logout - LastLogin does this automatically

Login Page Requirements:

  • import { LoginWithGoogleButton } from "https://esm.town/v/stevekrouse/LoginWithGoogleButton"
  • Use the <LoginWithGoogleButton /> React Component, optionally supplying the text attribute
  • Add "via LastLogin" underneath LoginWithGoogleButton, centered, secondary text, linking to https://lastlogin.io/
  • Pass the email from the server to the client-side code if using React hydration or similar techniques

For authenticated users:

  • Store email in context: c.set('userEmail', email)
  • In your authenticated views, add logout functionality: <a href="/auth/logout">Logout</a>

Public Routes Whitelist:

  • Add a PUBLIC_ROUTES array at the top for routes that should be accessible without authentication
  • Don't include /auth/logout in the whitelist (LastLogin handles it before your middleware runs)
  • Use for truly public routes like: /demo/:id, /api/health, static assets, etc.

If you encounter problems loading the React button, try this simple approach for authCheck.ts:

// Public routes that don't require authentication const PUBLIC_ROUTES = [ // Add public routes here as needed // '/demo/:id', // Example: public demo viewing // '/api/public', // Example: public API endpoints ]; // Helper function to check if a route is public function isPublicRoute(path: string): boolean { return PUBLIC_ROUTES.some((route) => { // Handle exact matches if (route === path) return true; // Handle wildcard patterns (basic implementation) if (route.includes("*")) { const pattern = route.replace("*", ".*"); return new RegExp(`^${pattern}$`).test(path); } // Handle route parameters (basic implementation) if (route.includes(":")) { const pattern = route.replace(/:[^/]+/g, "[^/]+"); return new RegExp(`^${pattern}$`).test(path); } return false; }); } export default async (c, next) => { // Allow public routes to bypass authentication if (isPublicRoute(c.req.path)) { await next(); return; } const email = c.req.header("X-LastLogin-Email"); if (!email) { // Show login page if not authenticated return c.html(` <!DOCTYPE html> <html> <head> <title>Your App - Login Required</title> </head> <body> <div style="border: 1px solid #ccc; padding: 20px 40px; float: left;"> <h1>Sign in</h1> <p>Sign in to access your app.</p> <div id="login-button-container"> <p>Loading login button...</p> </div> <p style="text-align: center; margin-top: 20px;"> <a href="https://lastlogin.io/" target="_blank" style="color: #888; font-size: 14px;">via LastLogin</a> </p> </div> <script type="module"> import { LoginWithGoogleButton } from "https://esm.town/v/stevekrouse/LoginWithGoogleButton"; import React from "https://esm.sh/react@18.2.0?deps=react@18.2.0"; import { createRoot } from "https://esm.sh/react-dom@18.2.0/client?deps=react@18.2.0,react-dom@18.2.0"; const loginContainer = document.getElementById('login-button-container'); if (loginContainer) { const root = createRoot(loginContainer); root.render(React.createElement(LoginWithGoogleButton, { text: "Sign in with Google" })); } </script> </body> </html> `); } // If authenticated, store email in context for use by routes c.set("userEmail", email); // Continue to the next middleware/route await next(); };

Common Gotchas

Logout Route:

  • ❌ Don't create a custom /auth/logout route in your Hono app
  • ✅ Just link to /auth/logout - LastLogin handles it automatically
  • The logout link will work for authenticated users without any custom code
  • LastLogin intercepts /auth/logout at the wrapper level before it reaches your middleware

Public Routes:

  • Use the whitelist in authCheck.ts for routes that should be accessible without authentication
  • /auth/logout doesn't need to be whitelisted (LastLogin handles it before your middleware runs)
  • Examples of routes that should be whitelisted: public demos, health checks, static assets

Authentication Headers:

  • Authenticated requests will have X-LastLogin-Email header
  • Unauthenticated requests will not have this header
  • LastLogin wrapper handles session validation before your code runs

Error Handling:

  • If LastLogin can't validate a session, it returns 401 before reaching your app
  • Your authCheck middleware only runs for requests that pass LastLogin's validation
  • No need to handle session expiration - LastLogin manages this

Townie, stop here! Before proceeding to additional steps, confirm that this step is working correctly.

If everything is working as intended: conclude this step with these messages:

  1. Briefly summarize the work that was just completed and why it's important
  2. Provide the URL to the dashboard endpoint so the user can see that the dashbaord is live. Tell the user that they can copy and paste that URL into a new tab to see it live.
  3. Tell the user that they can log into the dashboard using their Google email address.

Next step: webhook authentication

Tell the user that the next step is to add webhook authentication to this Val. Explain what webhooks are why it's important to authenticate them.

Lastly, tell the user to copy this line and paste it into Townie:

Add webhook auth to this Val with what you see in /_townie/03-webhooks.md

Go to top
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Product
FeaturesPricing
Developers
DocsStatusAPI ExamplesNPM Package Examples
Explore
ShowcaseTemplatesNewest ValsTrending ValsNewsletter
Company
AboutBlogCareersBrandhi@val.town
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.