Adding Authentication
to a Web Page
Wiring the shared Google OAuth library into any page on this site — four changes, nothing more.
Overview
The shared auth library at /js/auth/ handles Google OAuth login via a popup flow, session management through the API backend, and a ready-made user badge UI. All session state lives at https://api.rforssen.net.
Integrating it into a page requires exactly four small changes — a placeholder div, two imports, a setupAuthUI call, and gating your protected logic behind the returned auth object.
Prerequisites
-
✓
The shared auth files are deployed at
/js/auth/on the same origin. -
✓
Your page has a local
authUI.jsexportingrenderAuthBadgeandwireAuthUI. Copy from any existing app (e.g./autonomo/expenses/js/authUI.js) and adjust styling as needed. -
✓
The OAuth popup landing page exists at
/oauth-popup-complete.htmlat the site root.
The Four Steps
Place an empty div with id="authBadge" somewhere in the page body. The library will inject either a Sign-in button or a user pill with a logout menu into this element.
<div id="authBadge"></div>
<!-- typically top-right corner or nav bar -->html
At the top of your page's JS module, import from the shared absolute path and from your local authUI.js:
import { setupAuthUI } from "/js/auth/setupAuthUI.js";
import { renderAuthBadge, wireAuthUI } from "./authUI.js";js
/js/auth/setupAuthUI.js so it resolves correctly regardless of where your page lives in the directory tree.setupAuthUI fetches the current session, renders the badge, and wires the login/logout handlers. It returns an auth object describing the session state.
document.addEventListener("DOMContentLoaded", async () => {
const { auth } = await setupAuthUI({
renderAuthBadge,
wireAuthUI,
elId: "authBadge" // must match your div id
});
if (auth.authenticated) {
loadData(); // only run protected logic when logged in
}
});js
The returned auth object:
| Property | Type | Description |
|---|---|---|
| auth.authenticated | boolean | True if a valid session cookie exists |
| auth.is_authorized | boolean | True if the user is on the whitelist |
| auth.user | object | null | { name, email, role } when logged in |
| auth.role | string | 'superuser' | 'whitelisted' | 'public' |
Only invoke data-loading or privileged functions when the session check passes. Everything outside that block runs for all visitors.
// Basic: just check login
if (auth.authenticated) {
loadData();
}
// Role-based: restrict to whitelisted users
if (["superuser", "whitelisted"].includes(auth.role)) {
loadAdminPanel();
}js
Complete Minimal Example
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Page</title>
<script type="module" src="./js/index.js"></script>
</head>
<body>
<h1>My Protected Page</h1>
<div id="content"></div>
<div id="authBadge"></div> <!-- ① -->
</body>
</html>html
js/index.js
// ② imports
import { setupAuthUI } from "/js/auth/setupAuthUI.js";
import { renderAuthBadge, wireAuthUI } from "./authUI.js";
document.addEventListener("DOMContentLoaded", async () => {
// ③ call setupAuthUI
const { auth } = await setupAuthUI({
renderAuthBadge,
wireAuthUI,
elId: "authBadge"
});
// ④ gate protected logic
if (auth.authenticated) {
document.getElementById("content").textContent =
"Hello, " + auth.user.name;
}
});js
Shared Auth Library — File Reference
These files live at /js/auth/ and are shared across all apps on the site. Do not modify them per-app.
| File | Purpose |
|---|---|
| setupAuthUI.js | Main entry point. Orchestrates fetchAuth, badge rerender, and login/logout callbacks. This is the only file you import. |
| initAuth.js | Instantiates the auth client with the API base URL (https://api.rforssen.net) and the popup redirect URL. |
| authClient.js | Fetches session state via /auth/me, drives the login popup using BroadcastChannel, and handles the logout POST. |
Per-App File — authUI.js
Each app provides its own authUI.js exporting two functions. Copy from an existing app and adjust styling as needed.
renderAuthBadge({ elId, auth, onLogin })
Injects either a Sign-in button or an authenticated user pill (with logout menu) into the element identified by elId. Called on every auth state change.
wireAuthUI({ onLogout })
Attaches click handlers for the logout button and the pill toggle. Uses an internal _wired flag so it's safe to call multiple times — only the first call actually binds listeners.
_wired flag, if you ever remount the auth badge in a different context you may need to reload the page to re-wire the handlers.Troubleshooting
-
Page loads but nothing happens after login
Check the Network tab for 404s on
/js/auth/*.js. All three shared files must be present and reachable. -
Auth badge never renders
Confirm the
idon your div matches theelIdpassed tosetupAuthUI. The default is"authBadge". -
Login popup opens but auth never resolves
BroadcastChannelrequires both windows on the same origin. Verify/oauth-popup-complete.htmlexists at the site root and posts{ type: "auth:success" }on the correct channel name. -
Session not recognized after login
Cookies are
SameSite; confirm the page and the API are on the expected origins and that all fetch calls to the API includecredentials: "include". -
Deploy looks correct but still broken
NFS attribute caching (
actimeo) can delay file visibility to the web server by several seconds after a copy. Wait a moment and hard-reload before debugging further.