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.
Important: LastLogin automatically handles certain routes and authentication flows:
/auth/logout - LastLogin intercepts this route and handles logout automatically/auth/logout and LastLogin will clear the session and redirectX-LastLogin-Email header for authenticated requestsAuthentication Flow:
X-LastLogin-Email header and passes to your app/auth/logout requests → LastLogin clears session automatically (never reaches your app)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);
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:
/backend/routes/dashboard.tsximport dashboardRoute from "./backend/routes/dashboard.tsx";app.get("/", dashboardRoute)This approach:
main.tsx focused on app setup and middlewareThe dashboard route should:
c.get("userEmail"))/api/health endpoint results as raw JSON in a tag; e.g., JSON.stringify(data, null, 2)
/auth/logoutAuthentication Middleware:
const email = c.req.header("X-LastLogin-Email")/auth/logout - LastLogin does this automaticallyLogin Page Requirements:
import { LoginWithGoogleButton } from "https://esm.town/v/stevekrouse/LoginWithGoogleButton"<LoginWithGoogleButton /> React Component, optionally supplying the text attributeFor authenticated users:
c.set('userEmail', email)<a href="/auth/logout">Logout</a>Public Routes Whitelist:
PUBLIC_ROUTES array at the top for routes that should be accessible without authentication/auth/logout in the whitelist (LastLogin handles it before your middleware runs)/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();
};
Logout Route:
/auth/logout route in your Hono app/auth/logout - LastLogin handles it automatically/auth/logout at the wrapper level before it reaches your middlewarePublic Routes:
/auth/logout doesn't need to be whitelisted (LastLogin handles it before your middleware runs)Authentication Headers:
X-LastLogin-Email headerError Handling:
Townie, stop here! Before proceeding to additional steps, confirm that this step is working correctly.
If everything is working as intended, tell the user that they can test auth by opening up main.tsx in a preview and signing in with LastLogin.
After that, the user can merge this branch into main, and then proceed to the next step: webhook authentication. Don't write any code.