Nidhara – Security-First Auth Foundation for a Personal Finance & Growth Platform
NestJS, Prisma, and Next.js monorepo with Argon2 authentication, rotating refresh-token sessions, and audit logging.
Designed and built the monorepo, authentication engine, session lifecycle, Prisma data model, and documentation site alone.
My contribution
Solo owner & full-stack engineerDesigned and built the monorepo, authentication engine, session lifecycle, Prisma data model, and documentation site alone.
What I owned
- npm workspaces monorepo: NestJS backend, Next.js frontend, Docusaurus docs
- Signup, signin, refresh, and logout flows with Argon2 and dual-token issuance
- Database-backed sessions with CSRF verification and revocation on replay or logout
- Security event, account security state, and sudo session data models
- Swagger documentation, response envelope interceptor, and structured Pino logging
Product context
Individuals managing personal finances and life recordsGet authentication right before the ledger arrives.
Delivered the security foundation of a personal wealth and growth platform: Argon2 credentials, rotating refresh-token sessions with CSRF checks, audit events, and a documented monorepo ready for the finance ledger to be added.
Core challenge
Evidence: assumptionPersonal finance and journaling tools are fragmented, and many leave users with weak credential storage and fragile session handling. A financial platform needs an auditable authentication foundation, with strict session invalidation, CSRF defenses, and event logging, in place before any ledgers are connected.
User benefit matrix
The Value Proposition Canvas
Expand value canvas
Stolen or replayed session tokens could expose personal financial data
designedLong-lived refresh tokens in cookies with no replay detection
Single-use refresh tokens bound to a session record, rotated on every refresh and paired with a CSRF token
No audit trail of sign-in activity
measuredUnstructured console logs and no record of login outcomes
Security events (login success, login failure, logout, refresh rotation) stored with user-agent and IP metadata, plus structured JSON request logs
Proof
Impact and product screens
My results
Click any preview to open full size. Use arrow keys to browse.
Build
Stack and engineering choices
Technology
Frontend
- Next.js 16 (App Router)
- React 19
- TailwindCSS 4
- TypeScript
- Docusaurus 3 (docs site)
Backend
- NestJS 11
- Argon2
- Passport JWT
- cookie-parser
- nestjs-pino / pino-http
- Swagger (OpenAPI)
Database
- PostgreSQL 15
- Prisma ORM 7 (6 models, 2 migrations)
DevOps & Infra
- Docker Compose
- npm workspaces
- ESLint & Prettier
- Jest
Key decisions
Personal finance and journaling tools are fragmented, and many leave users with weak credential storage and fragile session handling. A financial platform needs an auditable authentication foundation, with strict session invalidation, CSRF defenses, and event logging, in place before any ledgers are connected.
Protect credentials and session secrets against GPU-accelerated brute-force attacks.
Decision: Used Argon2 for credential verification and refresh token hashes instead of bcrypt. Trade-off: higher memory use per authentication request.
Outcome: Stronger resistance to hardware-assisted brute force on stored secrets.
Long-lived refresh tokens in cookies risk theft and cross-site request forgery.
Decision: Bound each refresh token to a session record, rotated the refresh hash on every /auth/refresh call, and required a matching client csrfToken. Trade-off: extra database writes on refresh.
Outcome: Refresh token reuse is rejected and background refresh from another site is blocked.
Sensitive account changes need re-authentication without interrupting the ongoing session.
Decision: Modeled a standalone SudoSession with a time-bounded sudoUntil and a verified escalation method (PASSWORD, RECOVERY_KEY, MFA). Trade-off: sensitive endpoints will need a second authorization check.
Outcome: A data model for step-up authentication is in place for when sensitive endpoints are built.
Plain console logs are hard to search and weak as an audit trail.
Decision: Integrated nestjs-pino with pino-http and a global ResponseInterceptor to emit structured JSON logs and a uniform response envelope. Trade-off: some extra bootstrap configuration.
Outcome: Searchable request and response logs for auditing and debugging.
Trust
Security and roadmap
Security & reliability
- Argon2 hashing for passwords and refresh token hashes
- Dual-token flow: short-lived access token plus httpOnly refresh cookie
- Single-use refresh rotation with CSRF token verification; session revoked on replay or logout
- Security events (LOGIN_SUCCESS, LOGIN_FAILED, LOGOUT, REFRESH_ROTATED) stored with IP and user-agent
- Failed-login tracking and sudo session state modeled in the database
Future roadmap
- Passkey (WebAuthn) and security question authentication
- Financial accounts, immutable transaction ledgers, and savings goals
- Personal development suite (LifeLog, Capsule, Knowledge base, Micro Actions)
Technical Appendix
Nidhara is an npm workspaces monorepo with three workspaces: a NestJS 11 backend using Prisma and PostgreSQL, a Next.js 16 frontend, and a Docusaurus documentation site. This case study covers the authentication and session foundation, which is the part built so far. The finance ledger and personal growth features are planned, not shipped.
Architecture
flowchart TD
subgraph Mono ["Monorepo (npm workspaces)"]
FE["frontend (Next.js 16)"]
DOCS["docs-site (Docusaurus)"]
BE["backend (NestJS 11)"]
end
subgraph Pipeline ["Backend Pipeline"]
INT["ResponseInterceptor + Pino Logger"]
AUTHC["AuthController"]
APPC["AppController"]
GUARD["JWT Strategy & Guards"]
INT --> AUTHC
INT --> APPC
AUTHC --> GUARD
end
subgraph Services ["Application Services"]
AUTHS["AuthService (Argon2, token rotation)"]
TOK["AuthTokensService (JWT sign/verify)"]
USERS["UsersService"]
AUTHC --> AUTHS
AUTHS --> TOK
AUTHS --> USERS
end
subgraph Data ["PostgreSQL (Prisma)"]
MODELS[("6 Models")]
AUTHS --> MODELS
USERS --> MODELS
end
BE --> PipelineRequest flow: Client Request → ResponseInterceptor & Pino → Controller (dto/) → Service → PrismaService → PostgreSQL.
Data Model
| Model | Purpose |
|---|---|
User | Core account record |
AuthCredential | Stored credential data, hashed with Argon2 |
AuthSession | One record per session, holding the rotating refresh hash and CSRF verification state |
SecurityEvent | Immutable log of LOGIN_SUCCESS, LOGIN_FAILED, LOGOUT, and REFRESH_ROTATED with IP and user-agent |
UserAccountSecurityState | Tracks failed logins and account security status |
SudoSession | Time-bounded privilege escalation with a verified method (PASSWORD, RECOVERY_KEY, MFA) |
Refresh Rotation Flow
- On signin, the server issues a short-lived access token and an httpOnly refresh cookie, and stores a hash of the refresh token on a new
AuthSession. - The client calls
/auth/refreshwith the cookie and itscsrfToken. - The server verifies the refresh hash and the CSRF token against the session record.
- On success, it issues a new access token and a new refresh token, and replaces the stored hash, so the old token cannot be used again.
- A replayed token or a logout revokes the session, and a security event is recorded.
Scope & Status
The project is at prototype stage. The backend exposes 5 endpoints across 2 controllers and has one smoke-level test (app.controller.spec.ts). Passkey and security-question authentication, the sudo escalation endpoints, and all finance features are on the roadmap. The SudoSession and UserAccountSecurityState tables exist in the schema, but the flows that use them are not built yet.
By the Numbers
| Metric | Count |
|---|---|
| Git commits (solo) | 18 |
| Monorepo workspaces | 3 |
| REST endpoints | 5 across 2 controllers |
| Backend services | 4 (AuthService, AuthTokensService, UsersService, PrismaService) |
| Prisma models | 6 |
| Prisma migrations | 2 |
| Backend spec files | 1 (1 test case) |
| Documentation pages | 30 |
Case closed
More work.