PS
All projects
Completed2026

i-Bank

A banking API built around a strict auth boundary

NestJS backend for a banking client — JWT authentication via Passport, guarded account operations, and separated profile and preferences domains.

NestJSTypeScriptJWTPassportFirebase Admin

4

Feature modules

JWT + Passport

Auth

Auth service & controller

Tested

Overview

i-Bank is a NestJS API for banking operations, built as a study in getting the authentication boundary right before building anything on top of it.

Where DealPlate delegates identity wholesale to Firebase, i-Bank issues and verifies its own JWTs through Passport — a deliberately different approach to the same problem, and the reason the project was worth building.

Problem

Financial APIs have no forgiving failure mode. An endpoint that's accidentally public in a social app is embarrassing; the same mistake over account data is a breach.

The usual failure isn't a missing auth check — it's an inconsistent one: guards applied per controller, a helper someone forgets to call, a route added later that nobody remembers to protect.

Solution

Authentication is structural. A Passport JWT strategy validates tokens, a JwtAuthGuard applies the check, and protected routes declare that guard rather than each handler re-deriving the caller's identity.

Domains are kept narrow and separate — accounts, profile and preferences are distinct modules, so account operations don't quietly grow profile-editing powers.

DTOs with class-validator define the request contract explicitly, and the auth service and controller are the parts that carry unit tests, because they're the parts where a regression is most expensive.

Features

JWT authentication

Token issuance and verification through @nestjs/jwt with a Passport strategy, rather than hand-rolled token handling.

Guarded routes

JwtAuthGuard applied as a structural concern, so protection is declared rather than remembered.

Account operations

A dedicated accounts module holding balance and account-level logic behind the auth boundary.

Profile management

User profile handling kept separate from account operations.

Preferences

Its own module — settings shouldn't share a surface with money.

Validated DTOs

Typed request objects define the contract at the edge of every endpoint.

Firebase Admin integration

Available alongside the JWT strategy for identity verification where a federated token is the better fit.

Unit-tested auth

Spec coverage on the auth service and controller — the highest-consequence code in the project.

Tech Stack

Framework

  • NestJS
  • TypeScript

Auth

  • @nestjs/jwt
  • Passport
  • passport-jwt
  • Firebase Admin

Validation

  • @nestjs/mapped-types
  • DTOs

Runtime

  • Node.js
  • Express platform

Tooling

  • ESLint flat config
  • Jest

Architecture

A conventional layered NestJS service with the auth boundary pulled out as its own concern: strategy, guard and module, consumed by feature modules rather than reimplemented inside them.

  1. auth/

    JWT strategy, guard, controller, service and DTOs — plus the project's unit tests.

    Passport@nestjs/jwt
  2. guards/

    Shared guards available across modules, keeping protection composable.

    NestJS
  3. accounts/

    Account operations behind the auth boundary, in controller/service/module form.

    NestJS
  4. profile/ + preferences/

    Separate narrow domains, deliberately not merged into a single 'user' module.

    NestJS
  5. config/

    Environment and application configuration isolated from feature code.

    @nestjs/config

Folder Structure

src/
NestJS application root
├── main.ts
Bootstrap
├── app.module.ts
Module composition
├── auth/
jwt.strategy.ts, jwt-auth.guard.ts, controller, service, dto/, specs
├── guards/
Shared guards used across feature modules
├── accounts/
Account operations — controller, service, module
├── profile/
Profile domain
├── preferences/
User preferences domain
└── config/
Environment configuration

API Flow

Credentials in, token out, token verified on every subsequent call — with the verification applied structurally rather than per handler.

  1. Client authenticates

    The auth controller accepts credentials against a validated DTO.

  2. Service issues a JWT

    @nestjs/jwt signs a token carrying the user claim.

  3. Client sends the bearer token

    Subsequent requests carry it in the Authorization header.

  4. Passport strategy validates

    passport-jwt extracts and verifies the token, resolving the user from its payload.

  5. JwtAuthGuard admits or rejects

    Protected routes declare the guard; unauthenticated requests never reach a handler.

  6. Feature service executes

    Account, profile or preference logic runs with an already-established identity.

Challenges

Making protection impossible to forget

The realistic failure mode isn't writing a bad auth check, it's adding a route six weeks later and not writing one at all. Pushing verification into a guard and strategy means a new endpoint's protection is a declaration in its decorator rather than a block of code someone has to remember to paste.

Two identity systems in one codebase

The project carries both a self-issued JWT strategy and Firebase Admin. Keeping both meaningful meant being clear about which owns which surface, rather than letting them overlap into an ambiguous middle where it's unclear what actually authenticated a request.

Deciding what deserved tests

Testing everything in a project this size is a poor use of effort; testing nothing is worse. Coverage went to the auth service and controller, on the reasoning that a silent regression there is the only one that's genuinely dangerous.

Learnings

Passport strategies are worth the indirection — they turn 'is this request authenticated' into configuration rather than logic.

Narrow modules age better than broad ones. Splitting profile from preferences from accounts looked like overkill and stopped being overkill almost immediately.

Building a self-issued JWT flow after building a Firebase-delegated one clarified what each actually gives you: control versus not owning credential storage.

DTOs are documentation that can't drift, because the contract is enforced at runtime.

Future Improvements

Refresh token rotation with revocation — the obvious gap in any access-token-only design.

Persistence behind the account module, with transactional guarantees appropriate to balance changes.

An immutable transaction ledger rather than mutable balance fields.

Rate limiting and lockout on the authentication endpoints.

Integration tests covering the guard, not just unit tests covering the service.

Structured audit logging for every account-affecting operation.