The four weeks we kept losing
Every B2B SaaS engagement we delivered started the same way.
Week one and two: wire ASP.NET Core Identity, plug in OAuth2 or OIDC,
decide how tenant isolation works, design the company-membership model,
write the audit log scaffolding.
Week three: integrate Stripe (customer, checkout, portal, webhook
receiver, reconciliation worker), figure out where signing keys live,
how to rotate them, how to handle race conditions on webhook delivery.
Week four: stand up an admin shell, pick a UI kit, decide what a "page
pattern" looks like in this codebase, write the first three pages, get
the design system to a point where the rest of the team can build on it.
By the time the team could focus on the feature the customer was paying
for, a month was gone. Every project. Every time.
What we did
We extracted that four weeks into a reusable foundation and made it the
starting point of every new engagement. We called it Postiba because it
sounds opinionated and we are.
Concretely, the foundation ships:
- Identity + OAuth/OIDC server in two separate hosts, so your JWTs
carry a real
iss claim and the Blazor WASM client uses the standard
authorization-code-plus-PKCE flow against a real authorization server.
- Multi-tenancy as a convention. Inherit from
TenantEntity, the
global EF filter takes care of scoping every query. SystemAdmin
impersonation overlay for support.
- Stripe subscriptions wired end-to-end, including the webhook
patterns most teams get wrong on the first attempt: race-safe dedup,
outbound idempotency keys, out-of-order delivery handling,
reconciliation worker with twelve documented safety nets.
- Audit pipeline built on EF interceptors and an outbox table. Mark
an entity
[Auditable], save the row, the audit event is written in
the same transaction.
- Admin shell in Blazor WebAssembly on our own component library,
dense and flat by design. A handful of certified page patterns the team
copies from when adding pages.
What changes
The first measurable change is the kickoff itself. The first standup
of a new project is no longer "let me get the project to compile";
it's "here's the workspace, here's the user, here's the audit log,
where are we starting on the actual feature."
The second is more subtle. Because the foundation is the same across
projects, lessons learned on one engagement come back to all of them.
Last quarter we hit a Stripe webhook ordering bug on a customer
deployment. The fix went into the foundation. Two projects later, a
different team didn't have to discover the same bug, the fix was
already there, with a documented reason next to the code.
What we explicitly avoided
We had three rules during extraction.
No magic. Every convention is documented as a rule file under
.agents/rules/. AI agents picking up the codebase reproduce the same
patterns instead of inventing new ones. Humans reading a piece of code
can grep for the rule that explains why it's there.
No premature abstraction. Two similar pieces of code stay two
similar pieces of code. We extract on the third occurrence, not the
second. The codebase has very few interfaces, the ones that exist
are justified by two or more production implementations, never "for
testability".
No runtime plugin loading. Modules compose at compile time. Adding
or removing a module is a dotnet sln command, a <ProjectReference>
edit, and one line in Program.cs. We considered runtime plugins and
walked away, the AssemblyLoadContext dance was not worth what it
would have cost us in cognitive load.
What we did not promise
A foundation is not a finished product. The first day of a Postiba fork
still requires you to brand the website, write your features, and
think hard about what differentiates your offering. Authentication is
done; the actual reason a customer pays you, you still have to build.
But you start building it on day one.