Authentication Flow and Security

**Referenced Files in This Document** - [worker.js](file://worker.js) - [alliance-login.njk](file://src/alliance-login.njk) - [newsletter-spam-protection.js](file://src/assets/js/modules/newsletter-spam-protection.js) - [main.js](file://src/assets/js/main.js) - [wrangler.jsonc](file://wrangler.jsonc) - [cloudflare-pages.toml](file://cloudflare-pages.toml)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion

Introduction

This document explains the complete authentication flow and security implementation for the Invest Australia Alliance members portal. It covers the magic link authentication process from email submission through session establishment, cryptographic security measures, email validation, bot protection, and the lifecycle of tokens and sessions. It also documents KV namespace usage, cookie security settings, and practical examples and troubleshooting guidance.

Project Structure

The authentication system is implemented in a Cloudflare Worker that serves both static assets and handles protected routes. The frontend login page is generated by an Eleventy template and uses a honeypot field to deter bots. The Worker manages:

  • Magic link issuance and validation
  • Session creation and verification
  • KV-backed token storage with TTL
  • Cookie security settings
graph TB
subgraph "Frontend"
Login["Login Page<br/>alliance-login.njk"]
JS["Newsletter Spam Protection<br/>newsletter-spam-protection.js"]
end
subgraph "Cloudflare Worker"
W["worker.js"]
KV1["KV: MEMBER_EMAILS"]
KV2["KV: MAGIC_TOKENS"]
end
subgraph "External Services"
Resend["Resend Email API"]
end
Login --> W
JS --> Login
W --> KV1
W --> KV2
W --> Resend

Diagram sources

  • [worker.js:1-321](file://worker.js#L1-L321)
  • [alliance-login.njk:1-73](file://src/alliance-login.njk#L1-L73)
  • [newsletter-spam-protection.js:1-24](file://src/assets/js/modules/newsletter-spam-protection.js#L1-L24)

Section sources

  • [worker.js:1-321](file://worker.js#L1-L321)
  • [alliance-login.njk:1-73](file://src/alliance-login.njk#L1-L73)
  • [newsletter-spam-protection.js:1-24](file://src/assets/js/modules/newsletter-spam-protection.js#L1-L24)
  • [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)
  • [cloudflare-pages.toml:1-17](file://cloudflare-pages.toml#L1-L17)

Core Components

  • Cloudflare Worker entrypoint and route handlers for authentication and protected routes
  • Frontend login page with honeypot field and client-side feedback
  • Newsletter spam protection module that injects a hidden field into external forms
  • KV namespaces for approved member emails and one-time magic tokens
  • HMAC-SHA256-based session token signing and constant-time verification
  • Cryptographically secure random token generation

Section sources

  • [worker.js:1-321](file://worker.js#L1-L321)
  • [alliance-login.njk:1-73](file://src/alliance-login.njk#L1-L73)
  • [newsletter-spam-protection.js:1-24](file://src/assets/js/modules/newsletter-spam-protection.js#L1-L24)
  • [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)

Architecture Overview

The authentication architecture centers on a Cloudflare Worker that:

  • Validates incoming requests to protected routes
  • Issues magic links upon valid email submission
  • Verifies magic link tokens and issues secure session cookies
  • Enforces cookie security policies and redirects unauthorized users
sequenceDiagram
participant U as "User Agent"
participant L as "Login Page<br/>alliance-login.njk"
participant W as "Worker<br/>worker.js"
participant KV as "KV Namespaces"
participant R as "Resend API"
U->>L : "Open /alliance/login/"
L-->>U : "Render login form with _gotcha"
U->>W : "POST /alliance/login/ {email, _gotcha}"
W->>W : "Validate form and email regex"
W->>KV : "Lookup MEMBER_EMAILS for membership"
alt "Approved member"
W->>W : "Generate cryptographically secure token"
W->>KV : "Store token : <hex> -> email (TTL)"
W->>R : "Send magic link email"
else "Not approved or invalid"
W-->>U : "Redirect with 'sent=1'"
end
U->>W : "GET /alliance/verify/?token=<hex>"
W->>KV : "Fetch token : <hex>"
alt "Valid and present"
W->>KV : "Delete token : <hex>"
W->>W : "Create session token (HMAC-SHA256)"
W-->>U : "302 Redirect to /alliance/members/ with Set-Cookie"
else "Invalid/expired"
W-->>U : "302 Redirect to /alliance/login/?error=expired"
end

Diagram sources

  • [worker.js:77-177](file://worker.js#L77-L177)
  • [alliance-login.njk:21-34](file://src/alliance-login.njk#L21-L34)

Detailed Component Analysis

Magic Link Authentication Flow

  • Email submission: The login page renders a form with a hidden honeypot field. On submit, the Worker validates the email format and checks the honeypot to reject automated submissions.
  • Token generation: For approved members, the Worker generates a 32-byte cryptographically secure random token, stores it in the MAGIC_TOKENS KV with a 15-minute TTL, and sends an email containing a verification link.
  • Verification: Clicking the link invokes the verify endpoint, which retrieves the token from KV, deletes it to enforce one-time use, creates a signed session token, and sets a secure session cookie.
flowchart TD
Start(["POST /alliance/login/"]) --> Validate["Validate form and email regex"]
Validate --> Honeypot{"Honeypot empty?"}
Honeypot --> |No| BotDetected["Reject (bot)"]
Honeypot --> |Yes| Lookup["Lookup MEMBER_EMAILS"]
Lookup --> Found{"Member approved?"}
Found --> |No| ShowSent["Show 'check your email' message"]
Found --> |Yes| GenToken["Generate cryptographically secure token"]
GenToken --> StoreKV["Store token:<hex> -> email (TTL)"]
StoreKV --> SendMail["Send magic link email"]
SendMail --> ShowSent
BotDetected --> ShowSent
ShowSent --> Verify["GET /alliance/verify/?token=<hex>"]
Verify --> FetchKV["Fetch token:<hex>"]
FetchKV --> Exists{"Exists and valid?"}
Exists --> |No| Expired["Redirect to login with error=expired"]
Exists --> |Yes| DeleteKV["Delete token:<hex>"]
DeleteKV --> SignSession["Create signed session token (HMAC-SHA256)"]
SignSession --> SetCookie["Set HttpOnly; Secure; SameSite=Lax; Max-Age=30d"]
SetCookie --> Members["302 Redirect to /alliance/members/"]

Diagram sources

  • [worker.js:97-147](file://worker.js#L97-L147)
  • [worker.js:153-177](file://worker.js#L153-L177)
  • [alliance-login.njk:21-34](file://src/alliance-login.njk#L21-L34)

Section sources

  • [worker.js:97-147](file://worker.js#L97-L147)
  • [worker.js:153-177](file://worker.js#L153-L177)
  • [alliance-login.njk:1-73](file://src/alliance-login.njk#L1-L73)

Cryptographic Security Measures

  • HMAC-SHA256 signature verification: The Worker signs a payload containing email and expiry using a server-side secret and verifies signatures using constant-time comparison to prevent timing attacks.
  • Constant-time comparison: The verification routine compares signature lengths and performs bitwise XOR comparisons across the entire signature to avoid timing leaks.
  • Secure token generation: Tokens are generated using cryptographically secure random values suitable for cryptographic use.
flowchart TD
P["Payload: email|expiry"] --> Sign["HMAC-SHA256(sign)"]
Sign --> CompareLen{"Signature length equal?"}
CompareLen --> |No| Fail["Reject"]
CompareLen --> |Yes| Loop["XOR compare each byte"]
Loop --> DiffZero{"diff == 0?"}
DiffZero --> |Yes| OK["Accept"]
DiffZero --> |No| Fail

Diagram sources

  • [worker.js:39-58](file://worker.js#L39-L58)

Section sources

  • [worker.js:20-58](file://worker.js#L20-L58)

Email Validation and Enumeration Obfuscation

  • Email validation: The Worker enforces a basic email format check before proceeding.
  • Enumeration obfuscation: The response is identical whether the email is approved or not, preventing attackers from enumerating members by observing different outcomes.

Section sources

  • [worker.js:111-116](file://worker.js#L111-L116)

Honeypot Protection Against Bots

  • Hidden field: The login form includes a hidden input named _gotcha. If it receives a non-empty value, the submission is rejected.
  • Frontend injection: The newsletter spam protection module injects a hidden field into external forms to reduce spam.

Section sources

  • [alliance-login.njk:22-22](file://src/alliance-login.njk#L22-L22)
  • [newsletter-spam-protection.js:10-16](file://src/assets/js/modules/newsletter-spam-protection.js#L10-L16)

Token Lifecycle

  • Generation: 32 bytes of cryptographically secure randomness are converted to a hex string and stored in MAGIC_TOKENS KV.
  • Storage: Key format is token:, value is the member’s email, with a 15-minute TTL.
  • One-time use: On successful verification, the token is deleted immediately to prevent reuse.

Section sources

  • [worker.js:118-121](file://worker.js#L118-L121)
  • [worker.js:158-161](file://worker.js#L158-L161)

Session Creation and Verification

  • Session token: Contains email and expiry, encoded and signed with HMAC-SHA256.
  • Cookie security: HttpOnly, Secure, SameSite=Lax, 30-day Max-Age.
  • Verification: The Worker decodes the token, checks expiry, recomputes the HMAC, and performs constant-time comparison.

Section sources

  • [worker.js:32-37](file://worker.js#L32-L37)
  • [worker.js:163-171](file://worker.js#L163-L171)
  • [worker.js:39-58](file://worker.js#L39-L58)

KV Namespaces and Configuration

  • MEMBER_EMAILS: Stores approved member emails under keys formatted as member:.
  • MAGIC_TOKENS: Stores one-time tokens under keys formatted as token: with TTL.
  • Configuration: KV namespaces must be bound in the Worker configuration; secrets must be set via the CLI.

Section sources

  • [worker.js:8-10](file://worker.js#L8-L10)
  • [wrangler.jsonc:17-26](file://wrangler.jsonc#L17-L26)

Dependency Analysis

The authentication system depends on:

  • Cloudflare Worker runtime for routing and cryptography
  • KV namespaces for membership and tokens
  • Resend API for sending emails
  • Static assets binding for serving the login page
graph LR
W["worker.js"] --> ME["MEMBER_EMAILS KV"]
W --> MT["MAGIC_TOKENS KV"]
W --> RS["Resend API"]
W --> AS["Static Assets Binding"]
L["alliance-login.njk"] --> W
NSP["newsletter-spam-protection.js"] --> L

Diagram sources

  • [worker.js:1-321](file://worker.js#L1-L321)
  • [alliance-login.njk:1-73](file://src/alliance-login.njk#L1-L73)
  • [newsletter-spam-protection.js:1-24](file://src/assets/js/modules/newsletter-spam-protection.js#L1-L24)
  • [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)

Section sources

  • [worker.js:1-321](file://worker.js#L1-L321)
  • [wrangler.jsonc:1-35](file://wrangler.jsonc#L1-L35)

Performance Considerations

  • KV latency: Each request reads from KV for membership and token verification; ensure KV namespaces are provisioned and monitored.
  • Email delivery: External API calls introduce network latency; consider retry/backoff strategies if needed.
  • Token TTL: Short-lived tokens reduce KV storage overhead and improve security.
  • Static assets: Serving static pages through the Worker reduces cross-origin complexity.

[No sources needed since this section provides general guidance]

Troubleshooting Guide

Common issues and resolutions:

  • KV namespaces not configured: The Worker returns a 503 response indicating missing KV bindings. Confirm KV namespaces are created and bound in the Worker configuration.
  • Missing secrets: SESSION_SECRET and RESEND_API_KEY must be set via the CLI; without them, the Worker cannot sign tokens or send emails.
  • Invalid or expired token: The verify endpoint redirects to the login page with an appropriate error parameter; instruct users to request a new magic link.
  • Bot protection triggers: If the hidden field is filled, the submission is rejected; verify the frontend injection is active and not blocked by ad blockers.
  • Cookie not set: Ensure HTTPS is used and SameSite/Lax compatibility is respected by the browser; verify the redirect includes the Set-Cookie header.

Section sources

  • [worker.js:70-75](file://worker.js#L70-L75)
  • [worker.js:98-98](file://worker.js#L98-L98)
  • [worker.js:155-159](file://worker.js#L155-L159)
  • [alliance-login.njk:57-72](file://src/alliance-login.njk#L57-L72)

Conclusion

The authentication system implements a robust, secure magic link flow with strong cryptographic foundations, bot protection, and careful cookie security. By leveraging Cloudflare Worker primitives, KV namespaces, and a simple frontend template, it provides a streamlined and secure experience for members while mitigating common attack vectors.