OAuth Architecture

rforssen.net – Authentication Strategy, Principles & Design

Table of Contents

Authentication Mental Model

Principle

This is how mature OAuth-backed platforms are designed: Google provides identity one time, the platform enforces trust continuously.

Authentication Flow

1️⃣ Popup navigates to /auth/login/google
2️⃣ That request redirects the popup to Google
3️⃣ Google authenticates the user
4️⃣ Google redirects the popup to /auth/login/google/callback
Backend now:
– verifies state
– verifies nonce
– exchanges auth code for tokens
– extracts user identity
– creates Flask session cookie (stores user identity securely)
5️⃣ Backend redirects popup to oauth-popup-complete.html
6️⃣ That page notifies the main window via BroadcastChannel
7️⃣ Popup closes
8️⃣ Main page now knows the user is logged in
9️⃣ From now on every backend request automatically includes the session cookie — so the backend always knows who the user is, simply by verifying the cookie.

This document defines how authentication works across the rforssen.net platform.
Different apps have different security needs, so the platform deliberately supports two authentication models.

Roland maintains multiple environments:

Authentication Strategies

No single authentication pattern fits all of them. Therefore the platform supports two complementary authentication strategies:

1️⃣ In-App Authentication
2️⃣ Edge Authentication

1️⃣ In-App Authentication
User-Aware UI + Filtered Content

Objective

Use this strategy when the page or app should:

Typical use cases:

Model

User opens page → Page loads → Page calls /auth/me
If authenticated → personalize UI
If not authenticated → show login + limited content

Backend still enforces actual security.

Backend Requirements

Google OAuth + Flask session cookies.

EndpointPurpose
/auth/login/googleStart login
/auth/login/google/callbackComplete login
/auth/meCheck who user is
/auth/logoutLog out

/auth/me Contract

If user is NOT authenticated:

{
  "authenticated": false
}

If user IS authenticated:

{
  "authenticated": true,
  "user": {
    "email": "...",
    "name": "...",
    "sub": "google-id"
  },
  "role": "me | family | public"
}

Frontend Responsibilities

Frontend role logic improves UX.
Backend checks provide real security.

Backend Security Rules

Cookie Security Requirements

SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"

Redirect Safety

Do NOT allow redirect to arbitrary domains.

Only allow trusted redirect destinations.

Summary

Use In-App Authentication when you want public + authenticated users, personalization, roles, and mixed-audience experiences.

🔁 Authentication Invocation Model (Frontend)

This section explains where authentication is actually invoked in a frontend application, and which parts are reusable vs page-specific.

Mental Model

Authentication in rforssen.net is event-driven, not global.

There is no background authentication process running continuously. Instead, authentication is invoked explicitly at a small number of well-defined points.

The Three Authentication Entry Points

Every in-app authenticated page invokes authentication in exactly three situations:

1️⃣ Page Load (Initial State)

When the page loads, it probes authentication state to decide:
onload →
  fetchAuth() →
    /auth/me →
      renderAuthBadge()
      decide page behavior
2️⃣ After Popup Login (Dynamic State Change)

When login happens in a popup window, the main page is notified via BroadcastChannel.

Instead of duplicating logic, the page simply re-runs the same authentication bootstrap.
BroadcastChannel "auth:success" →
  initAuth() →
    same logic as page load
This guarantees:
3️⃣ Logout (Explicit Session Destruction)

Logout is an imperative action:
logout click →
  POST /auth/logout →
    session cleared →
      page reload →
        /auth/me → unauthenticated

What Does NOT Invoke Authentication

The following actions do not trigger authentication checks:

Authentication is always explicit and intentional.

Reusable vs Page-Specific Code

Layer Responsibility Reusable?
Auth Core fetchAuth(), OAuth popup flow, /auth/me, /auth/logout ✅ Yes (shared across all apps)
Page Bootstrap Call fetchAuth(), render auth badge, decide public vs private behavior ⚠️ Small, repeated per page
Page Logic Application-specific functionality ❌ No (page-specific)

Minimal Pattern for a New Page

Any new page that wants in-app authentication only needs this:

import { fetchAuth, renderAuthBadge } from "./js/auth.js";

window.addEventListener("load", async () => {
  const auth = await fetchAuth();
  renderAuthBadge(auth);

  if (!auth.authenticated) return;

  // Page-specific logic starts here
});

Design Principle

Authentication is a gate, not a background service.
Pages decide when to check it.
Backend always enforces it.

This keeps the platform predictable, debuggable, reusable, and difficult to break accidentally.


2️⃣ Edge Authentication
Hard Gate / Full Access Blocker

Objective

Use this when:

Typical use cases:

Model

Request → Traefik → oauth2-proxy → Application

If not authenticated → block
If authenticated → allow access

Implementation Summary

Optional Identity Injection

oauth2-proxy can inject headers:

X-Auth-User
X-Auth-Email

Backend may use them — or ignore them.

Behavior Rules

Summary

Use Edge Authentication when you need strict perimeter security, platform protection, and hard blocking of all unknown users.

Which Strategy To Use?

NeedStrategy
User-facing experienceIn-App Auth
Public + Private mixedIn-App Auth
Roles & permissionsIn-App Auth
Family experiencesIn-App Auth
Admin toolsEdge Auth
phpMyAdminEdge Auth
Internal dashboardsEdge Auth
No UI unless logged inEdge Auth

Platform Philosophy

Platform / Admin / Infrastructure
➡ Edge Authentication
Family Apps / User Experiences
➡ In-App Authentication

They are not competing approaches — they are deliberately chosen tools, each excellent at its job.

Future Enhancements (Optional)