How-to guide

Adding Authentication
to a Web Page

Wiring the shared Google OAuth library into any page on this site — four changes, nothing more.

Auth API: api.rforssen.net
Method: Google OAuth popup
Library: /js/auth/

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.js exporting renderAuthBadge and wireAuthUI. 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.html at the site root.

The Four Steps

1
Add the auth badge placeholder to your HTML

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
2
Import setupAuthUI and your local authUI helpers

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
Note: Use the absolute path /js/auth/setupAuthUI.js so it resolves correctly regardless of where your page lives in the directory tree.
3
Call setupAuthUI on DOMContentLoaded

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:

PropertyTypeDescription
auth.authenticatedbooleanTrue if a valid session cookie exists
auth.is_authorizedbooleanTrue if the user is on the whitelist
auth.userobject | null{ name, email, role } when logged in
auth.rolestring'superuser' | 'whitelisted' | 'public'
4
Gate your protected logic behind auth.authenticated

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.

FilePurpose
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.

Note: Because of the _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 id on your div matches the elId passed to setupAuthUI. The default is "authBadge".
  • Login popup opens but auth never resolves BroadcastChannel requires both windows on the same origin. Verify /oauth-popup-complete.html exists 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 include credentials: "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.