StaticAuth: Design Notes on a Static-Site Auth SaaS

Why I spent a Sunday evening designing a micro-SaaS that probably won't get built

I have a folder on my machine called static-auth-saas/. It contains an architectural design for a small product: deploy a static site behind Google authentication with one command. Think surge.sh with identity bolted on. It is unlikely this will ever be built. That is exactly why the design doc is interesting — there is no commercial pressure to lie about how well the parts fit together. This is the article I wish I could read before I had written the doc myself.

Note: The full design doc lives at ~/openclaw-backup/workspace/static-auth-saas/ (~30 KB, 600 lines, 4 ADRs, 12 sections). This article is a working-through-the-ideas version, not a re-statement of the spec. The numbers and architecture are real; the opinions about them are mine.

The Idea

Static sites are easy. surge.sh, Vercel, Netlify, Cloudflare Pages — pick your poison, run one command, get a URL. The hard part is putting something in front of it — a sign-in wall, a private docs portal, an internal staging environment. Today the options are:

The gap is: a tool that gives you the surge.sh experience (one command, declarative config in git, no dashboard) for static sites that need identity. The target market is the long tail of internal docs portals, pre-launch landing pages with email-restricted sign-in, and small SaaS staging environments.

Cost-First Architecture

The most important design constraint is cost. A static-site auth SaaS at $5/month pricing cannot afford $1/month per customer on CloudFront distributions alone. The first architectural decision is therefore not "what is the cleanest design" — it is "what is the cheapest sustainable design."

One CloudFront distribution, many sites

The v1 design gave every customer their own CloudFront distribution. It is the obvious design — full isolation, native custom domains, no coupling between tenants. The problem is the price tag: every distribution carries a $1/month minimum, billed even if the site gets zero traffic.

Approach Cost (100 sites) Cost (1,000 sites) Custom domains
Per-site distribution (v1) $100/mo $1,000+/mo Native CNAME per dist
Shared distribution (v2) $1.00/mo $1.00/mo Shared dist, multiple aliases (quota 250, raisable to 2,500)

How to read this: per-site = one distribution per customer (default AWS pattern). Shared = one distribution with a wildcard cert on *.staticauth.dev, path-based routing in Lambda@Edge.

The v2 design puts every customer behind a single *.staticauth.dev wildcard cert, then routes by subdomain inside a single Lambda@Edge function. The function reads the Host header, looks up the site config from DynamoDB (60-second in-memory cache), and rewrites the URI to /sites/<slug><path> before forwarding to a single S3 bucket.

The cost saving is roughly 80%+ at 100 sites, 99% at 1,000 sites. The trade-off is real though: one misconfigured Lambda@Edge takes down every site, and "noisy neighbour" traffic spikes are now a shared problem. The mitigation is canary deployment of the edge function — version it, route 5% of traffic, monitor, then ramp. CloudFront does not offer a native blue/green for Lambda@Edge associations, so this is closer to a manual staged rollout than a true canary, but it is enough.

Auth: Lambda@Edge, Not Signed Cookies

CloudFront signed cookies are tempting because they are free and have no cold start. The catch is they only answer "does this request have a valid signed cookie" — they do not give you a login flow, an OAuth handshake, or per-user identity. For a "sign in with Google" experience you have to build all of that yourself anyway, which negates the simplicity advantage.

Lambda@Edge Viewer Request is the right call, despite the constraints:

The interesting engineering is keeping the bundle small and the p99 low. The auth function does four things: read the host header, look up the site config, validate the JWT, return a redirect or a rewrite. There is no reason this should exceed 200 ms on a warm path.

The Custom Sign-In Page

This is the part of the design I am most curious about. Cognito gives you a hosted UI for free — it handles the OAuth dance, the JWKS endpoint, the token refresh. The problem is it is ugly and it shows AWS branding. URL looks like auth.us-east-1.amazoncognito.com. The hosted UI is also visually unchangeable beyond a logo and a handful of CSS variables, so you cannot match the look of the site you are protecting.

The v2 design splits the problem: Cognito handles the crypto and OAuth plumbing, and a custom HTML page hosted in S3 handles the login UX. The Lambda@Edge auth check redirects unauthenticated visitors to /__auth/login.html, which renders "Sign in with Google" buttons, kicks off the OAuth flow, and lands on /__auth/callback.html to set the cookie and redirect back to the original page.

The unavoidable wrinkle: even with a custom sign-in page, the user briefly touches a Cognito URL when the OAuth code exchange happens. It lasts under a second, and the user sees Google's consent screen rather than Cognito's, but it is not a fully branded experience. That is the trade-off you make for not implementing JWKS, token rotation, and refresh-token handling yourself.

The honest summary: if your priority is "the login page looks like the rest of my product," Cognito's hosted UI is a dead end. If your priority is "I do not want to maintain an OAuth server in 2026," a custom front-end on top of Cognito is the sweet spot.

Silent Token Refresh

The first version of the design said "re-authenticate on token expiry." That is a great way to get a user yanked to a Google login screen in the middle of reading documentation. v2 adds a silent refresh flow that I expect will save more support tickets than every other improvement combined.

The pattern is borrowed from OIDC silent renew: a hidden iframe points at /__auth/refresh.html, which uses the prompt=none parameter to get fresh tokens from Cognito without user interaction. If it works, the cookies are updated silently. If it fails (the Google session itself expired), a banner appears: "Session expired, click to re-authenticate."

The cookie strategy is worth noting: auth-token is short-lived (1 hour, HttpOnly) and validated on every request; refresh-token is long-lived (30 days, HttpOnly, path- restricted to /__auth/); and a separate auth-expiry cookie holds the JS-readable expiry timestamp so the refresh checker can detect impending expiry without parsing JWTs.

The Cost Model Has Hidden Costs

The visible cost of the architecture is roughly $1.50/month fixed (CloudFront + Route 53 + S3 + DynamoDB below free tier) plus $0.11 to $1.88 per site depending on traffic. That is the number you put in a pitch deck. The real number is higher, and the difference is mostly CloudWatch Logs.

Lambda@Edge logs every invocation to CloudWatch in the region where the function runs. At 50k requests per site per month, with even a minimal 1 KB log line per request, you are looking at 1.5 GB of logs per site per month. CloudWatch ingest is $0.50/GB. That is $0.75 per site per month, just for logs — more than the rest of the infrastructure combined for a low-traffic site.

The mitigation is structured logging with a log level that defaults to WARN, sampling in production, and a hard rule against logging request bodies or token contents. The takeaway: if you build this, the cost model is not the AWS service prices. It is the AWS service prices plus the implicit cost of observability. Budget accordingly.

AWS Quotas That Will Bite You

The design document has a table of every AWS quota that matters. The three I would plan for first:

None of these are dealbreakers. All of them are things you find out about at 2 AM when a customer cannot sign in and you do not know why. Reading the quota table before the design goes to production is the difference between a Tuesday incident and a Sunday incident.

Why Not Cloudflare?

The honest comparison section in the design doc goes through this in detail. The short version: Cloudflare Access + Cloudflare Pages is a genuinely good product for the right customer. If you are already on Cloudflare, it is almost certainly the right answer.

The cases where StaticAuth is the better fit:

Vendor lock-in is a real cost. The argument for StaticAuth is not "Cloudflare is bad" — it is "some teams have already made the AWS bet, and this gives them an auth option that does not require a second vendor relationship."

What the Design Doc Does Not Tell You

A few things I noticed that the spec punts on:

None of these change the architecture. They are the day-2 operational gotchas that the ADRs do not capture.

My Honest Take

The design is sound. The cost model is tight but workable. The authentication path is well-trodden — Cognito + Lambda@Edge + CloudFront is not a novel combination, and that is a feature, not a bug. The novel part is the developer experience: one command, one config file, no dashboard.

The reason this is unlikely to be built is not the design — it is the market. The customers who would use it are small (one to five sites, $5-29/month) and price-sensitive. The customers with bigger budgets already have Vercel or Cloudflare or Okta. The narrow band of "I want a private static site on AWS with Google auth and I do not want to wire it up myself" is real, but it might be too narrow to support a business.

Which is exactly why the design doc is useful as a design doc, even without a product behind it. The constraints are tight, the trade-offs are honest, and the parts that are not novel (Cognito, Lambda@Edge, CloudFront) are described in enough detail to be a useful starting point for someone who actually needs to build this. Maybe that is you. The folder is on this machine.