DEV Community

Cover image for I Built a Spring Boot 4 SaaS Starter Kit So You Never Have to Write Auth Boilerplate Again
Rakusasu Stack
Rakusasu Stack

Posted on

I Built a Spring Boot 4 SaaS Starter Kit So You Never Have to Write Auth Boilerplate Again

Every SaaS project starts the same way.

You open a new Spring Boot project and the first three weeks look like this:

  • Week 1: JWT auth, refresh tokens, BCrypt passwords
  • Week 2: Stripe webhooks, subscription states, idempotency
  • Week 3: Rate limiting, global error handling, API docs

You haven't shipped a single feature yet. You've just rebuilt infrastructure you've built five times before.

I got tired of it. So I spent two weeks building it once, properly, with tests, and packaged it as a downloadable starter kit.

Here's everything inside.


What's Included

🔐 Authentication

  • JWT login with 15-minute access tokens signed with HMAC-SHA256
  • Refresh token rotation — every use issues a new token and revokes the old one
  • Replay attack detection — reusing a revoked token revokes your entire session family
  • Brute-force protection — 5 failed logins locks the account for 15 minutes
  • Spring Security 7 with stateless sessions and lambda DSL

💳 Stripe Subscription Management

  • Webhook receiver with HMAC-SHA256 signature validation
  • Handles subscription.created, subscription.updated, subscription.deleted, invoice.payment_failed
  • Full idempotency guard — duplicate Stripe events are safely ignored
  • Mock mode for local dev — no real Stripe account needed to get started
  • Subscription states: ACTIVE, CANCELED, PAST_DUE, TRIALING

🛡️ Production Hardening

  • Per-IP rate limiting via Bucket4j — 100 req/min default, 10 req/min on login
  • 429 Too Many Requests with Retry-After and X-RateLimit-* headers
  • Global exception handler — every error returns a structured JSON envelope
  • Stack traces never appear in API responses
  • BCrypt password hashing at strength 12

📦 Developer Experience

  • OpenAPI 3 / Swagger UI with JWT Bearer pre-configured — click Authorize and test immediately
  • Docker Compose — one command gets Postgres 16 running
  • Flyway migrations V1 to V4 — schema fully version controlled
  • MapStruct DTO mapping — entities never leak into API responses
  • 18 passing integration tests

The Tech Stack

Layer Technology Version
Framework Spring Boot + Spring Security 4.0.8 / 7.0
Language Java 17
Database PostgreSQL 16
Migrations Flyway 11
JWT JJWT 0.12.7
DTO Mapping MapStruct 1.6.3
API Docs springdoc-openapi 3.1.1
Rate Limiting Bucket4j 8.19.0
Container Docker Compose + Dockerfile

How the Refresh Token Rotation Works

Raw refresh tokens are never stored. Only their SHA-256 hash goes into the database.

Every time a client uses a refresh token, the server marks the old one as revoked and issues a new one. If someone tries to reuse a revoked token — which indicates the token was stolen — the server revokes the entire token family for that user. Every session is terminated immediately.

if (token.isRevoked()) {
    // Replay attack detected — revoke entire family
    refreshTokenRepository.revokeAllByUserId(token.getUser().getId());
    throw new InvalidTokenException(
        "Token reused. All sessions revoked for security.");
}
Enter fullscreen mode Exit fullscreen mode

How the Stripe Webhook Idempotency Works

Stripe delivers webhooks at least once. Without protection, a user could end up billed twice or receive two ACTIVE subscriptions.

Every processed event ID is stored in a stripe_events table. Before any business logic runs, the handler checks if the event was already processed.

if (stripeEventRepository.existsByStripeEventId(eventId)) {
    log.debug("Duplicate event {} — skipping", eventId);
    return; // Already processed, return 200 silently
}
Enter fullscreen mode Exit fullscreen mode

The unique constraint on stripe_event_id in the database is the safety net against race conditions.


The Database Schema

Four Flyway migrations ship out of the box. Hibernate ddl-auto is set to none — Flyway owns the schema, always.

  • V1users table with BCrypt hash, failed login counter, account lock columns
  • V2refresh_tokens with SHA-256 hash storage and cascade delete
  • V3subscriptions with Stripe customer and subscription ID columns
  • V4stripe_events as an idempotency log

The 13 API Endpoints

Method Path Auth Description
POST /api/v1/auth/register None Register new user
POST /api/v1/auth/login None Login, get tokens
POST /api/v1/auth/refresh None Rotate refresh token
POST /api/v1/auth/logout Bearer Revoke refresh token
GET /api/v1/auth/me Bearer Current user profile
GET /api/v1/users/me Bearer Get own profile
PUT /api/v1/users/me Bearer Update own profile
GET /api/v1/subscriptions/me Bearer Subscription status
POST /api/v1/webhooks/stripe Signature Stripe event receiver
GET /api/v1/admin/users ADMIN List all users
GET /api/v1/admin/users/{id} ADMIN Get user by ID
PATCH /api/v1/admin/users/{id}/role ADMIN Change user role
GET /actuator/health None Health check

Quick Start After Downloading

1. Purchase and download the zip file from the link at the bottom of this post

2. Unzip it anywhere on your machine

3. Copy .env.example to .env and fill in your values

DB_PASSWORD=your_password
JWT_SECRET=your_base64_secret
STRIPE_WEBHOOK_SECRET=mock
Enter fullscreen mode Exit fullscreen mode

4. Make sure Docker Desktop is running, then start Postgres

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

5. Navigate to the saas-starter folder and run the start script

cd saas-starter
.\run.ps1
Enter fullscreen mode Exit fullscreen mode

The script automatically loads your .env, checks Docker, waits for Postgres to be healthy, and starts the app.

6. Open Swagger UI

http://localhost:8080/swagger-ui.html
Enter fullscreen mode Exit fullscreen mode

All 13 endpoints are ready to test. Click Authorize, paste your JWT token, and every protected endpoint is unlocked.


Extending the Kit

The kit is intentionally focused on the core production foundation. Here are natural next additions you can build on top of it:

  • Email verification — the enabled column and lock_until are already in the schema, just needs an email sender bean
  • Password reset — the token pattern from refresh tokens applies directly
  • Redis-backed rate limiting — swap the in-memory map for a Bucket4j Redis store for multi-instance deployments
  • Multi-tenancy — a tenant_id column plus a Hibernate filter covers most use cases

The architecture is clean enough that adding any of these is straightforward — no spaghetti to untangle first.


Get the Full Kit

The complete source code — migrations, Docker setup, Dockerfile, tests, and README — is available as a zip download here:

👉 Spring Boot 4 SaaS Starter Kit

Starts at $43 for solo developers. One-time purchase — download, own it, extend it however you want.


Top comments (1)

Collapse
 
krusenas profile image
Karolis

Nice article! I built Webhook Relay because I got tired of losing Stripe events during deploys or downtime. Durable delivery with retries is a core part of it, so a failed subscription event waits in a queue instead of vanishing. Keep the duplicate event check though, since retries mean the same event can land twice.

You can add it for example to your docker-compose.yaml (webhookrelayd container like in this github.com/helixml/helix/blob/main... project)

Same for CI, it can live inside github actions, jenkins etc. All webhooks will just work.