Strict CSP with a 128-bit random nonce per request; script-src uses 'strict-dynamic' and does not include 'unsafe-inline'; 'unsafe-eval' is added only in development
nextjs-security-starter — Next.js Starter Template
By calliarc
A security starter template built on the Next.js App Router, featuring CSP nonce, secure response headers, Auth.js session authentication, CSRF protection, token bucket rate limiting and Zod input validation, with Vitest tests and a GitHub Actions pipeline.
Explore the design
Compare the page layouts and mobile experience. Open any screenshot for a closer look.
Actual screenshot captured by the server browser.
All pages & views 3
Screenshots show visual appearance; interactive behavior may differ.
What stands out
A quick read of the recorded project description.
Security response headers defined centrally: HSTS, nosniff, Referrer-Policy, Permissions-Policy, COOP/CORP, frame-ancestors, and removal of X-Powered-By
Auth.js v5 authentication: 8-hour JWT sessions, production cookies with the __Secure- prefix, scrypt password hashing, per-account login rate limiting, and callback URLs restricted to same-site addresses
CSRF protection: Server Actions rely on Origin validation; Route Handlers use secureRoute() to perform Origin checks and signed double-submit token validation
Token bucket rate limiting per client IP: login allows a burst of 5 then 5 per minute; /api/* allows a burst of 30 then 1 per second; responses include RateLimit-Limit and RateLimit-Remaining
Zod input validation and secure error handling; unknown errors return only a requestId and a generic message, without exposing stack traces to the client
Template Details
This is a Next.js security starter template. The README marks its status as working v0.1.0; the template builds, tests pass, and all listed features are in place. It consolidates common web security mechanisms into a reusable code structure: src/proxy.ts generates a 128-bit random nonce per request and sets a strict CSP (script-src uses 'strict-dynamic' and does not include 'unsafe-inline'; 'unsafe-eval' is added only in development), while also handling secure response headers, per-client-IP rate limiting and optimistic auth redirects. Security headers are defined centrally in src/lib/security/headers.ts and applied to all paths by next.config.ts. Authentication is based on Auth.js v5, using JWT sessions (8-hour lifetime, renewed at most once per hour); in production, cookies carry the __Secure- prefix and are set with Secure, HttpOnly, SameSite=Lax; the credentials provider validates input with Zod, hashes passwords with scrypt, performs hashing even for unknown emails to reduce timing differences, and rate-limits logins per account. For CSRF, Server Actions rely on Next.js Origin validation, while Route Handlers must be wrapped with secureRoute(), which performs Origin checks and signed double-submit token validation. Rate limiting uses a token bucket: the login endpoint allows a burst of 5 then 5 per minute, and /api/* allows a burst of 30 then 1 per second. Input validation uses Zod, and error handling via handleRouteError() distinguishes Zod errors, ApiError and unknown errors, without returning stack traces to the client. The template also provides /.well-known/security.txt (RFC 9116, Expires always 180 days out), npm audit in CI, gitleaks and Dependabot.
Limitations and caveats: The README explicitly states that the nonce applies only to dynamic rendering, and the root layout calls connection(), so no page is statically prerendered. The default MemoryStore works per process, resets after deployment and is not shared across instances or Serverless invocations; multi-instance deployments require implementing the RateLimitStore interface with Redis and keeping consume atomic using Lua scripts or Upstash's @upstash/ratelimit. The client IP is taken from X-Real-IP / X-Forwarded-For and is only trustworthy behind a proxy that overwrites these headers (such as Vercel, Cloudflare or a correctly configured nginx / load balancer). The demo user is read from environment variables; the README recommends replacing findUser() with a database query and considering argon2id or bcrypt. The roadmap items—Redis / Upstash adapter, CSP violation reporting endpoint and Playwright end-to-end tests—are not yet complete. The README does not provide performance data, a production deployment checklist or specific configuration instructions for hosting platforms.
Is it right for your project?
Use Cases
- Quickly setting up a foundation for CSP nonce, secure response headers and CSRF protection in a Next.js App Router project
- Referencing an Auth.js v5 implementation of credentials login, session cookie configuration and protected routes
- Adding a per-IP token bucket rate limiting example for login and API endpoints
- Learning how to integrate npm audit, gitleaks and Dependabot in CI
Suitable For
- Developers using the Next.js App Router with TypeScript
- Teams that need a runnable reference implementation of security mechanisms rather than a production-ready product
- Projects planning to replace the demo user store and connect a database
- Engineers interested in the configuration details of CSP, CSRF, rate limiting and security response headers
Getting started & customization
Use these recorded setup notes alongside the repository instructions for your chosen version.
- 1
Requires Node.js 20.9 or higher (CI uses Node 22) and npm; after cloning the repository, run npm ci and copy .env.example to .env.local
- 2
Generate AUTH_SECRET with npx auth secret, and use npm run hash-password to generate a scrypt hash for DEMO_USER_PASSWORD_HASH
- 3
After running npm run dev, visit /login and sign in with DEMO_USER_EMAIL and the hashed password; /dashboard shows a Server Action form and a Route Handler form using a CSRF token
- 4
To enable GitHub login, create an OAuth app with the callback URL http://localhost:3000/api/auth/callback/github and set AUTH_GITHUB_ID and AUTH_GITHUB_SECRET
- 5
For multi-instance deployments, the default MemoryStore is not shared; implement the RateLimitStore interface with Redis as described in the README
License & source material
Usage conditions & limitations
Free to use · keep license noticeThis entry covers the free version of a public repository under the MIT license; the free scope is the code of that version and does not imply that external services or third-party assets are free.
Must comply with when using
- Free use, modification, commercial use and distribution of this version's code are permitted.
- When distributing copies or substantial portions of the code, retain the original author's copyright notice and the full MIT license notice.
- The code is provided as is; warranties and limitations of liability are governed by the original license and applicable law.
Additional costs & items to verify
- Demo images, fonts, icons and other third-party assets must be checked against their respective licenses; the code's MIT license alone is not sufficient to judge them.
- External services such as domains, hosting, databases, payments or AI may incur separate charges.
- The nonce applies only to dynamic rendering, and the root layout calls connection(), so no page is statically prerendered.
- The default MemoryStore works per process, resets after deployment and is not shared across instances or Serverless invocations; multi-instance deployments require implementing the RateLimitStore interface with Redis.
- The client IP is taken from X-Real-IP / X-Forwarded-For and is only trustworthy behind a proxy that overwrites these headers (such as Vercel, Cloudflare or a correctly configured nginx / load balancer).
- The demo user is read from environment variables; findUser() must be replaced with a database query, and switching to argon2id or bcrypt should be considered.
- Enabling GitHub login requires creating an OAuth app and setting AUTH_GITHUB_ID / AUTH_GITHUB_SECRET.
- The Redis / Upstash RateLimitStore adapter is not yet implemented and remains a roadmap item.
- The CSP violation reporting endpoint (report-to) is not yet implemented and remains a roadmap item.
- Playwright end-to-end tests for the authentication and CSRF flows are not yet implemented and remain a roadmap item.
View Original Text Basis
Permission is hereby granted, free of charge
Original License Text
- Nonces only work with dynamic rendering, so the root layout calls `connection()`. This means no page is statically prerendered.
Original README
**Rate limiting in production:** the default `MemoryStore` works per process. It resets on deploy and is not shared across instances or serverless invocations. For multi-instance deployments, implement the `RateLimitStore` interface on top of Redis. Use a Lua script (or Upstash's `@upstash/ratelimit`) so that consume stays atomic, then pass that store to the `RateLimiter` instances.
Original README
Client IPs come from `X-Real-IP` / `X-Forwarded-For`. These headers can only be trusted behind a proxy that overwrites them, such as Vercel, Cloudflare, or a correctly configured nginx or load balancer.
Original README
The demo user is read from env vars. Replace `findUser()` with a database lookup, and consider argon2id or bcrypt through your user store.
Original README
To enable GitHub sign-in, create an OAuth app with the callback `http://localhost:3000/api/auth/callback/github` and set `AUTH_GITHUB_ID` / `AUTH_GITHUB_SECRET`.
Original README
- [ ] Redis / Upstash `RateLimitStore` adapter
Original README
- [ ] CSP violation reporting endpoint (`report-to`)
Original README
https://github.com/calliarc/nextjs-security-starter/blob/main/LICENSEhttps://github.com/calliarc/nextjs-security-starter/blob/main/README.md- [ ] End-to-end tests (Playwright) for auth and CSRF flows
Original README
Compiled by DeepSeek based on repository license and README ·2026-10-14. Results only cover the materials read; dependencies, images, fonts, and subsequent term changes are subject to their respective original texts.
Project checks & description sources
Template Information
- Source platforms
- GitHub
- Documentation and maintenance reference score
- More documented · 80 / 100
- Technologies
- Next.js · React
- Website type
- Starter
How this score is calculated
Our heuristic uses license records (up to 30), a demo link (12), a preview (10), stars (15), recorded maintenance (15), description completeness (8), feature records (5) and classification data (5). It is not a code, security or performance test, or a Google score. A link does not establish tested functionality.
What to verify for this project
- Repository update recorded
- 2026-10-14
- Screenshot last checked
- 2026-10-14
A demo link is listed. Check it with your own content and viewport sizes; its presence does not confirm that forms, authentication or payments work.
The catalog identifies Next.js as the primary stack. Check the actual dependency manifest and lockfile for versions and hosting requirements. These notes use recorded metadata, not a code audit or a deployment test.
Use a selection guide →How this description was prepared
AI-assisted description based on catalog records and repository material. Repository authors are not automatically the reviewers of this description.
Review declaration: codex-user-requested-batch-review-20261015 · 2026-10-15
Inspect the recorded source material
SHA-256: 00d6bef6c891102781b7dd0866b6d4819a8bb93d434b4bab63286dcbfe32e26a
2026-10-14T23:37:38.851Z
Source file / repository# Next.js Security Starter
Next.js template with secure headers, rate limiting, CSRF protection and auth set up correctly.
[](https://github.com/calliarc/nextjs-security-starter/actions/workflows/ci.yml)
[](LICENSE)
[](https://github.com/calliarc/nextjs-security-starter/releases)
[](https://www.calliarc.com/)
> **Status:** working v0.1.0. The template builds, the tests pass, and every feature below is in place. Feedback and issues are welcome.
## Features
- Strict Content-Security-Policy with a fresh nonce per request (`'strict-dynamic'`, no `'unsafe-inline'`)
- Security headers configured in one place (HSTS, nosniff, Referrer-Policy, Permissions-Policy, COOP/CORP, frame-ancestors)
- Authentication with secure session cookies (Auth.js v5, credentials provider plus optional GitHub OAuth, protected `/dashboard`)
- CSRF protection for server actions and API routes (Origin check plus a signed double-submit token for Route Handlers)
- Rate limiting for login and API endpoints (token bucket with a pluggable store)
- Input validation with Zod and safe error handling (no stack traces reach clients)
- `/.well-known/security.txt` (RFC 9116)
- Dependency and secret scanning in CI (`npm audit`, gitleaks, Dependabot)
## Tech stack
- Next.js 16 (App Router, `proxy.ts`)
- TypeScript (strict)
- Auth.js (`next-auth` v5)
- Zod 4
- Proxy-based (formerly "middleware") rate limiting
- Vitest, ESLint, GitHub Actions
## Getting started
Requirements: Node.js 20.9 or later (CI uses Node 22) and npm.
```bash
git clone https://github.com/calliarc/nextjs-security-starter.git
cd nextjs-security-starter
npm ci
cp .env.example .env.local
```
Fill in `.env.local`:
```bash
# 1. Session signing secret
npx auth secret # or: openssl rand -base64 32 -> AUTH_SECRET=
# 2. Demo user password hash (the prompt hides your input)
npm run hash-password # paste the output into DEMO_USER_PASSWORD_HASH=
```
Then run the app:
```bash
npm run dev # http://localhost:3000
```
Sign in at `/login` with `DEMO_USER_EMAIL` and the password you hashed. The dashboard shows a Server Action form and a Route Handler form that uses a CSRF token.
To enable GitHub sign-in, create an OAuth app with the callback `http://localhost:3000/api/auth/callback/github` and set `AUTH_GITHUB_ID` / `AUTH_GITHUB_SECRET`.
### Scripts
| Command | What it does |
| --- | --- |
| `npm run dev` | Development server |
| `npm run build` / `npm start` | Production build and server |
| `npm run lint` | ESLint (`eslint-config-next` core-web-vitals + TypeScript) |
| `npm run typecheck` | `tsc --noEmit` |
| `npm test` | Vitest unit tests for the CSP, rate-limit, CSRF and validation helpers |
| `npm run hash-password` | Prints an scrypt hash for `DEMO_USER_PASSWORD_HASH` |
### Project layout
```
src/
proxy.ts # nonce + CSP, security headers, rate limiting, optimistic auth redirect
auth.ts # Auth.js config (providers, cookies, session, redirect guard)
lib/security/
csp.ts # nonce generation and CSP builder
headers.ts # every other security header, in one place
csrf.ts # Origin check and signed double-submit tokens
rate-limit.ts # token bucket, RateLimitStore interface, MemoryStore
password.ts # scrypt hashing
client-ip.ts # client IP used as the rate-limit key
lib/http/ # safe error responses, secureRoute() wrapper
lib/validation/ # Zod schemas and a size-limited JSON body parser
app/
.well-known/security.txt/ # RFC 9116 security.txt
api/csrf, api/messages # CSRF token endpoint and an example protected endpoint
login, dashboard # sign-in page and protected page with Server Actions
tests/ # Vitest unit tests
```
## Security features explained
### Content-Security-Policy with nonces
`src/proxy.ts` creates a 128-bit random nonce for every request. It sets the CSP on the response, and on the request as well, which is how Next.js finds the nonce and adds it to its own scripts and styles. The policy looks like this:
```
default-src 'self'; script-src 'self' 'nonce-…' 'strict-dynamic'; style-src 'self' 'nonce-…';
object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests; …
```
- `'unsafe-eval'` is added **only** in development, because React's dev tooling needs it.
- Nonces only work with dynamic rendering, so the root layout calls `connection()`. This means no page is statically prerendered.
- To allow extra origins, use `cspDirectives({ extend: { "img-src": ["https://…"] } })`. The GitHub provider adds `https://github.com` to `form-action` automatically.
- API responses get `default-src 'none'; frame-ancestors 'none'`.
### Security headers in one place
All other headers are defined in `src/lib/security/headers.ts`. `next.config.ts` applies them to every path, and the proxy also applies them to the redirects and 429 responses it creates itself:
| Header | Value |
| --- | --- |
| `Strict-Transport-Security` | `max-age=63072000; includeSubDomains` (production only; `preload` is opt-in) |
| `X-Content-Type-Options` | `nosniff` |
| `Referrer-Policy` | `strict-origin-when-cross-origin` |
| `Permissions-Policy` | camera, microphone, geolocation, payment, usb, topics and others disabled |
| `X-Frame-Options` / CSP `frame-ancestors` | `DENY` / `'none'` |
| `Cross-Origin-Opener-Policy` / `-Resource-Policy` | `same-origin` |
| `X-Powered-By` | removed (`poweredByHeader: false`) |
### Authentication and session cookies
`src/auth.ts` configures Auth.js v5:
- JWT sessions last 8 hours and are re-issued at most once an hour.
- In production the cookies use the `__Secure-` prefix and are `Secure`, `HttpOnly` and `SameSite=Lax`.
- The credentials provider validates input with Zod and hashes passwords with scrypt. It runs the hash even for unknown emails, so response time does not reveal whether an account exists, and it returns the same error for a wrong email or a wrong password.
- The login is rate limited per account.
- The `redirect` callback and the login action only accept same-site callback URLs, which blocks open redirects.
- `/dashboard` is protected twice. The proxy does a quick cookie-presence redirect, and the page itself calls `auth()`. Server Actions and Route Handlers also call `auth()` themselves, because a proxy matcher is not a security boundary.
The demo user is read from env vars. Replace `findUser()` with a database lookup, and consider argon2id or bcrypt through your user store.
### CSRF protection
- **Server Actions:** Next.js only accepts `POST` for them and rejects any call whose `Origin` host differs from `Host` / `X-Forwarded-Host`. You don't need a token. If a reverse proxy rewrites the host, list the public host in `experimental.serverActions.allowedOrigins`. Note that a request with **no** `Origin` header is allowed through with a warning, so SameSite cookies are still your second layer.
- **Route Handlers:** wrap them in `secureRoute()` (`src/lib/http/secure-route.ts`). For unsafe methods it runs two checks:
1. **Origin check.** `Origin` (falling back to `Referer`) must match the app's own origin or an allow-list. `Sec-Fetch-Site: cross-site` is always rejected. `X-Forwarded-*` headers are ignored unless `TRUST_PROXY_HEADERS=true`.
2. **Signed double-submit token.** `GET /api/csrf` returns `<random>.<HMAC>`. It also sets the token in an `HttpOnly`, `SameSite=Strict` cookie, which is named `__Host-csrf` in production. The client sends the token back in `x-csrf-token`. The cookie and header must match, and the HMAC must verify against `CSRF_SECRET` (or `AUTH_SECRET` if `CSRF_SECRET` is not set).
If either check fails, the response is a generic `403`.
### Rate limiting
`src/lib/security/rate-limit.ts` implements a token bucket. Each key has a bucket of `capacity` tokens that refills at `refillPerSecond`. `src/proxy.ts` applies the limits per client IP:
- **Login** (`POST /login`, `/api/auth/callback/*`, `/api/auth/signin/*`): a burst of 5, then 5 per minute. `authorize()` applies the same limit per email address.
- **API** (`/api/*`): a burst of 30, then 1 request per second.
Responses include `RateLimit-Limit` and `RateLimit-Remaining`. Blocked requests get a `429` with `Retry-After`.
**Rate limiting in production:** the default `MemoryStore` works per process. It resets on deploy and is not shared across instances or serverless invocations. For multi-instance deployments, implement the `RateLimitStore` interface on top of Redis. Use a Lua script (or Upstash's `@upstash/ratelimit`) so that consume stays atomic, then pass that store to the `RateLimiter` instances.
Client IPs come from `X-Real-IP` / `X-Forwarded-For`. These headers can only be trusted behind a proxy that overwrites them, such as Vercel, Cloudflare, or a correctly configured nginx or load balancer.
### Input validation and safe errors
- Zod schemas live in `src/lib/validation/schemas.ts`. `parseJsonBody()` enforces `Content-Type: application/json`, a byte limit and valid JSON before it validates.
- `handleRouteError()` handles three cases:
- Zod errors: returns `400` with a list of field paths and messages.
- `ApiError`: returns its own status and a public message.
- Anything else: logs the full error server-side under a request id and returns `{ "error": { "code": "internal_error", "message": "Something went wrong.", "requestId": "…" } }`. Stack traces are never sent to clients.
- `error.tsx` / `global-error.tsx` show only the opaque `digest`. Server Action bodies are capped at 100 KB.
### security.txt
`/.well-known/security.txt` is served by a route handler. Its `Expires` field is always 180 days ahead. Set `SECURITY_CONTACT`, `SECURITY_POLICY_URL` and `APP_URL` to point it at your own disclosure process.
### CI and supply chain
`.github/workflows/ci.yml` runs these jobs on every push and pull request:
- lint, typecheck, test and build
- `npm audit --audit-level=high`
- a full-history gitleaks secret scan
Workflow permissions are read-only and checkout credentials are not persisted. Dependabot (`.github/dependabot.yml`) opens weekly updates for npm packages and GitHub Actions.
## Roadmap
- [x] Initial release
- [x] Documentation and examples
- [x] CI and automated tests
- [ ] Redis / Upstash `RateLimitStore` adapter
- [ ] CSP violation reporting endpoint (`report-to`)
- [ ] End-to-end tests (Playwright) for auth and CSRF flows
Have an idea? [Open an issue](https://github.com/calliarc/nextjs-security-starter/issues).
## Contributing
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
## Changelog
See [CHANGELOG.md](CHANGELOG.md).
## License
[MIT](LICENSE) © 2026 CalliArc
---
Built and maintained by [CalliArc](https://www.calliarc.com/). Need help with secure application development? [Talk to our team](https://www.calliarc.com/services/managed-cyber-security/).
Explore similar templates
Compare previews and descriptions within the same stack and category.

Next.js SaaS Starter
The official Next.js SaaS starter template, integrating email/password authentication, Stripe Checkout subscriptions, a dashboard with user/team CRUD, basic Owner/Member RBAC, and activity logging.
Next.js · SaaS

Next.js Commerce
Vercel's officially maintained Next.js App Router ecommerce starter with deep Shopify integration, offering a complete product catalog, cart, and checkout flow. Core logic is modular for swapping backends, and community provider versions exist for BigCommerce, Medusa, and Saleor.
Next.js · E-commerce

Next-js-Boilerplate
A developer-first Next.js 16 starter with Tailwind CSS 4 and TypeScript, supporting both App Router and Page Router.
Next.js · Starter

tailwind-nextjs-starter-blog
A Next.js and Tailwind CSS blog starter template using Contentlayer for Markdown/MDX, with built-in SEO, code highlighting, and tags. It targets personal technical blogs and migration from Jekyll/Hugo to React, and supports one-click Vercel deployment.
Next.js · Blog

next-forge
next-forge is a Turborepo-based Next.js template maintained by Vercel, positioned as a starting point for SaaS products.
Next.js · Starter

SaaS-Boilerplate
SaaS-Boilerplate is an open-source SaaS starter template built with Next.js App Router, React 19, TypeScript, Tailwind CSS, and Shadcn UI.
Next.js · SaaS
