ASP.NET Core 10 · React 19 · PostgreSQL

Ship your .NET
SaaS in 15 min

The production-ready SaaS boilerplate for .NET developers. Auth, Stripe, multi-tenancy, invitations, CSRF, CI/CD — all wired up. You add your domain logic. Nothing else.

One-time purchase · Source code · No subscription

// Add your entities in 3 steps

public class Player : BaseEntity, IMustHaveTenant
{
    public Guid OrganizationId { get; set; }
    public string Name { get; set; } = "";
}

// 1 line in AppDbContext
modelBuilder.Entity<Player>()
    .HasQueryFilter(p =>
        _tenant.Id.HasValue &&
        p.OrganizationId == _tenant.Id.Value);

// Done. Isolated per tenant. Forever.
built with .NET 10 EF Core 10 React 19 PostgreSQL 16 Stripe Testcontainers Railway

The problem

Every .NET SaaS starts with
9 weeks of boilerplate.

01
Auth & refresh tokens
JWT, HttpOnly cookies, SameSite config, refresh token rotation, replay detection, logout revocation. Every edge case.
~2 weeks DIY
02
Multi-tenancy
Shared DB, Global Query Filters, OrganizationId auto-assignment, middleware resolution. Miss one guard, you leak data.
~3 weeks DIY
03
Stripe + webhooks
Checkout sessions, webhook signature verification, subscription sync, cancel-at-period-end, Billing Portal. Painful to get right.
~1 week DIY
04
Invitation system
7-day tokens, SHA-256 hashing, account creation on acceptance, token rotation on resend, replay prevention. Deceptively complex.
~1 week DIY
05
Multi-org support
One account, multiple organizations. Deterministic org selection at login, in-session switching, OrgSwitcher sidebar. Always gets forgotten.
~1 week DIY
06
Security hardening
CSRF protection, rate limiting, BCrypt timing attacks, SHA-256 token hashing, startup validation, CSP headers.
~1 week DIY

What's included

Everything. Already working.

Not a skeleton. Not a tutorial. A codebase you can fork, rename, and ship — with your business logic on top.

DotPlate.dev/dashboard
DotPlate dashboard — members, billing, settings, invitation flow
Complete auth system
Register, login, logout, forgot/reset password. 7-day sliding refresh tokens with rotation and replay detection via PostgreSQL xmin. HMAC-SHA256 JWTs with issuer + audience validation.
BCryptJWTHttpOnly cookiesSHA-256 tokens
Row-level multi-tenancy
Shared database, automatic isolation via EF Core Global Query Filters. OrganizationId auto-assigned on SaveChanges. One line to add a tenant-scoped entity. You cannot accidentally leak data.
IMustHaveTenantGlobal Query FiltersEF Core
Stripe billing, fully wired
Checkout sessions, webhook handling (created/updated/deleted), cancel-at-period-end detection, Billing Portal for self-service. Subscription + plan update in a single atomic transaction.
Stripe.netWebhooksBilling Portal
Email invitation system
Admins send 7-day invitation links. Invitees create an account and join in one click. Token rotation on resend, atomic account + membership creation, replay prevention. Pending invitations listed and revocable.
SHA-256 tokens7-day expiryResendAtomic
Multi-org & in-session switching
One account, multiple organizations. Deterministic org selection at login via LastActiveOrganizationId. Switch orgs from the sidebar without re-logging in — new JWT issued, no page reload required.
OrgSwitcherPOST switch-organizationJWT rotation
Clean Architecture, 4 layers
Domain / Application / Infrastructure / Api. No MediatR. Direct service injection. Code any .NET developer recognises on day one — zero learning curve for your team.
Clean ArchitectureIOptions<T>FluentValidation
React 19 dashboard
TypeScript strict, Tailwind v4, shadcn/ui, Sonner toasts. Login, register, forgot/reset password, accept invitation. Dashboard with members, billing, settings, and org switcher.
React 19Viteshadcn/uiTailwind v4
CI/CD + Docker, production-ready
GitHub Actions with independent pipelines for API and web. Smoke test hits /health after each API deploy. Multi-stage Dockerfile with non-root user. Auto-migrations on startup with 5-retry loop.
GitHub ActionsRailwayDocker

Time saved, feature by feature

Feature DIY estimate DotPlate
Auth + refresh tokens~2 weeks
Multi-tenancy + isolation~3 weeks
Stripe + webhooks~1 week
Email invitation system~1 week
Multi-org + org switching~1 week
CSRF + security hardening~1 week
Emails (Resend)~2 days
Integration tests~1 week
CI/CD + Docker~3 days
Frontend dashboard~1 week

The stack

Enterprise-grade .NET.
Modern React. No compromise.

Backend
ASP.NET Core 10Web API, middleware pipeline
EF Core 10Code First, Global Query Filters
PostgreSQL 16xmin concurrency for tokens
BCrypt.NetPassword hashing, timing-safe
FluentValidationEvery DTO, field-level errors
Stripe.netPayments, webhooks, portal
ResendTransactional emails, HTML templates
Frontend
React 19 + ViteTypeScript strict mode
Tailwind CSS v4CSS-first config, no config.js
shadcn/uiComponents you own the code of
React Router v7Protected routes, 404 handling
Axios interceptorsAuto-refresh, CSRF injection
SonnerToast notifications
Infrastructure
GitHub ActionsIndependent API + web pipelines
RailwayAPI, web, PostgreSQL managed
DockerMulti-stage, non-root user
TestcontainersReal PostgreSQL in CI, not mocks
xUnit + FluentAssertions62 integration test cases

Security

Not an afterthought.
Baked in.

Every security decision is documented with the reasoning behind it. You understand what's protecting your users and why.

🔐
BCrypt for passwords
Intentionally slow. Dynamic dummy hash prevents timing attacks on login — even if the email doesn't exist in the database.
🔑
SHA-256 for tokens
Deterministic, searchable by WHERE clause. SHA-256 on already-random 32-byte tokens — BCrypt on tokens enables DoS attacks.
🛡️
CSRF double-submit pattern
Token stored in memory only — never localStorage. Global middleware, exemptions explicit via [CsrfExempt]. Stripe webhooks and refresh exempt by design.
🔄
Refresh token rotation
PostgreSQL xmin detects concurrent replay. Cookie + DB expiry aligned to the day. All sessions revoked on org deletion or password change.
🏢
Tenant isolation at DB layer
Global Query Filters on every tenant-scoped entity. HasValue guard on every filter. Auto-assignment in SaveChanges — you cannot forget the OrganizationId.
✉️
Invitation token security
32 random bytes, SHA-256 hashed before storage. Token rotated on resend. AcceptedAt set atomically with membership — replay impossible at DB level.
Startup validation
IOptions<T> with ValidateOnStart on all services. App refuses to start with missing or invalid config. A missing JWT secret fails loudly — not in prod at 3am.
📧
Email enumeration blocked
Forgot password always returns HTTP 200. Password reset rate-limited to 3/hour/email — silent after that. No way to enumerate which emails exist.
Rate limiting
5 req/min on auth endpoints, 200 req/min global per IP. Configurable, applied at middleware level — no per-controller attribute noise.
🔀
Org switch verified server-side
POST /api/auth/switch-organization checks membership in the target org before issuing new cookies. The frontend cannot self-assign to an org it doesn't belong to.

Test coverage

Integration tests on real
PostgreSQL. Not mocks.

Testcontainers spins up an actual database in Docker. Global Query Filters, migrations, and concurrency tokens are tested against the real engine — not a memory stub that hides bugs.

11
Auth tests
Register, login, refresh rotation, replay prevention, revoke on logout
4
Multi-org tests
Login org selection, fallback to most-recent, switch org JWT, non-member rejection
12
Invitation tests
Send, resend (token rotation), accept existing user, accept new user atomically, replay prevention, revoke, preview, expiry
6
Multi-tenant tests
Isolation, auto-assignment, IgnoreQueryFilters, no-context throws InvalidOperationException
8
Member tests
Add by email, duplicate rejection, remove, self-remove guard
7
Password reset tests
Full flow, replay, enumeration protection, rate limit, token invalidation
6
Stripe webhook tests
Created, updated, cancel-at-period-end, deleted, unknown org, invalid ID
8
Org + user tests
Profile, update, slug conflict, change password, duplicate email guard

Pricing

One price. Yours forever.

No subscription. No seat licenses. Ship unlimited products with a single purchase.

What the license means
Use DotPlate to build unlimited commercial products. You may not resell or redistribute DotPlate itself as a template or starter kit. The source code you receive is yours to modify freely.

FAQ

Questions we actually get

Is this production-ready or just a tutorial codebase? +
It's production-ready. Multi-tenancy is enforced at the DB layer via EF Core Global Query Filters. BCrypt, SHA-256 tokens, CSRF middleware, JWT issuer/audience validation, Stripe webhook signature verification, rate limiting, typed options with startup validation, and auto-migrations are all configured. It runs on Railway and has been validated against real PostgreSQL in CI.
Why .NET and not Node/Supabase? +
Because you're a .NET developer. There are 70+ Next.js boilerplates. There are very few production-ready ones for .NET. DotPlate gives you the enterprise-grade stack you already know — Clean Architecture, EF Core, BCrypt — paired with React 19. No context switch, no learning curve, no debt.
How does the invitation system work? +
Admins send an invitation email from the Members page. The invitee receives a 7-day link. If they already have an account, clicking the link adds them to the org. If they're new, they create a password and their account + membership are created atomically in a single transaction. Re-sending an invitation rotates the token — the old link is immediately invalid.
Do I need a Stripe account before I can run it? +
Yes, but Stripe test mode works immediately — no real payments until you go live. The app will refuse to start if Stripe keys are missing or invalid (ValidateOnStart). With accounts already set up, you can be running locally in 30 minutes.
Can I swap PostgreSQL for SQL Server? +
Yes. Change the EF Core provider in DependencyInjection.cs and regenerate migrations. The only PostgreSQL-specific feature is the xmin concurrency token on refresh tokens — the docs explain how to replace it with [Timestamp] for SQL Server.
Can I add Google/GitHub OAuth? +
Yes. ASP.NET Core's authentication middleware supports any OAuth provider. Add the NuGet package and register it in Program.cs. The User entity already has OAuthProvider and OAuthProviderId fields. Detailed guidance is in the buyer documentation.
What does the single-developer license mean? +
One developer, unlimited commercial products. You can build and sell as many SaaS products as you want using DotPlate as the base. You cannot resell DotPlate itself as a template or boilerplate to other developers.

Stop rebuilding
the plumbing.

Every .NET developer who wants to build a SaaS has rebuilt the same auth, Stripe, and multi-tenancy code from scratch. You don't have to. Buy once, ship forever.

Get DotPlate — from €149

One-time · Source code · No subscription